Pages

Friday, December 27, 2013

Visio 2013 fill color problem

Recently there was a huge change in my professional life. I`ve moved to another city and found a new job. If for my previous employment I was working on WinForms application (and mostly on Visio integration into it) then now I am a back-end (mostly) web developer. So the focus of my posts will be moved to web questions and solutions.

But for now I want to post my last (probably) note about Visio. One of the problems I faced with Visio 2013 was introduction of the FillGradientEnabled param. Here is a link on MSDN. There is nothing special about it but it interfere  with a common way of filling with FillColor. If you will try to fill a shape with setting FillColor to some value it wont show if the FillGradientEnabled is there and is not False. It took a little for me to figure it out. The special case was with Organisational Chart shapes, as all of them from Visio 2013 have that property inherited from the theme...

So for me the solution was to check if that property is there, and in that case set it to false. After it FillColor worked as before!

Binding enum to WPF combobox

Many times I found myself binding combobox to a set of values. A common scenario is that those values are just strings - representation of a application state, so creation of a static collection of strings was not very attractive for me. Enums are naturally fit in this scenario. But how to bind combobox to the enum? To comfortably bind enum its items should have descriptions - they will be used as a values for combobox. But here is a small helper to do this:
public static class EnumHelper
{
  public static string Description(this Enum eValue)
  {
    var nAttributes = eValue.GetType().GetField(eValue.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);

    if (!nAttributes.Any())
      return eValue.ToString();

    var descriptionAttribute = nAttributes.First() as DescriptionAttribute;
    if (descriptionAttribute != null)
      return descriptionAttribute.Description;

    return eValue.ToString();
  }

  public static IEnumerable<KeyValuePair<Enum, string>> GetAllValuesAndDescriptions<TEnum>() where TEnum : struct, IConvertible, IComparable, IFormattable
  {
    if (!typeof(TEnum).IsEnum)
      throw new ArgumentException("TEnum must be an Enumeration type");

    return from e in Enum.GetValues(typeof(TEnum)).Cast<Enum>()
           select new KeyValuePair<Enum, string>(e, e.Description());
  }
}


public enum WorkModes
{
  [Description("Simple mode")]
  Simple,
  [Description("Complex mode")]
  Complex,
  [Description("Third (optional) option")]
  Etc
}

//from windows loaded event - bind itemsSource of combobox to enum.
//and set it to default value
private void Window_Loaded(object sender, RoutedEventArgs e)
{
  cbCurrentState.ItemsSource = EnumHelper.GetAllValuesAndDescriptions();
  cbCurrentState.SelectedIndex = 0;
}

//And combo box declaration:

Wednesday, November 27, 2013

How to get MAC address

If you need to get MAC addresses of installed network adapters, then you can use this:

using System.Net.NetworkInformation;

NetworkInterface[] arr = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface item in arr)
{
    PhysicalAddress mac = item.GetPhysicalAddress();
}

Tuesday, July 2, 2013

Stackoverflow parody

As you might know, there is a super usefull site: stackoverflow. I am active user of this community and it helps a lot in my work.
However, recently I have found this picture:
I think, it is adorably!!

Thursday, June 13, 2013

Detecting that shape is being copied in the visio control

Let`s say you are developing c# application that is using visio control for drawings.
Easy, scalable with the only major drawback that users will have to install MS Visio on their PCs.

Today I had to determine if the new shape is being copied rather then created from stencil. For example, you may want to copy some properties to shape from the original and if this shape is new it needs to be initialized somehow (those properties can`t be setted in the stencil). You can do it in the ShapeAdded event of the page object. But how to check if the shape is a copy? The answer is simple - check the Application.IsInScope[] property. Here is a little snippet:

private void page_ShapeAdded(Shape Shape)
{
    //it might be simple copy by Copy-Paste
    //or a drag-copied shape
    bool shapeIsACopy = VisioApplication.IsInScope[(int)VisUICmds.visCmdUFEditPaste] || 
              VisioApplication.IsInScope[(int)VisUICmds.visCmdEditPasteSpecial] || 
              VisioApplication.IsInScope[(int)VisUICmds.visCmdDragDuplicate]; 

    if (Shape.Master.NameU == MyShapes.TargetedShapeMaster && !shapeIsACopy)
        SetDefaultProperties(Shape);
}

Wednesday, May 29, 2013

Safe iterator for List

Sometimes you want to write something like this:
foreach (var element in collection)
    if (SomeCondition(element)) collection.Remove(element);

This example will throw InvalidOperationException, as you cannot change the collection while iterating throught it with default iterator. Sure, you can transform this example into simple LINQ:
collection.RemoveAll(element => SomeCondition(element));

But there might be situations, when you would choose iterators. In this case you would need to implement the safe iterator - which allows you to change the collection during enumeration.

And this can be simply done:
public class MyList: List
{
    new public IEnumerator GetEnumerator()
    {
        List currentState = new List(this);
        foreach (T element in currentState)
            yield return element;
    }
}

[TestMethod()]
public void MyList_Test()
{
    MyList<int> list = new MyList<int>();
    list.Add(1);
    list.Add(2);

    foreach (int num in list)
        list.Add(num * 3);

    Assert.IsTrue(list.Count == 4);
    Assert.IsTrue(list[3] == 6);
}

As you can see - all you need to do is to change the GetEnumerator method, so it would enumerate the safe copy of your collection.

Friday, April 5, 2013

Redirection of the Input/Output for processes

It is very comfortable to have redirected input and output for your console processes. For example, lets imagine a situation when you have to start scripts from your application (it might be something like script manager). So you have a collection of running processes and in order to handle them properly you have to start them with different arguments, get their messages, handle (or log) their exceprions.
Fortunately there is an easy way to achieve it. We can use RedirectStandartOutput, RedirectStandartInput and RedirectStandartError properties of the ProcessStartInfo class.
First of all we have to set those properties to true, then we have to set UseShellExecute propertie to false (because it allows us to redirect streams) and, finally, process different events. And do not forget to start listening to those streams!
Here is a simple example to run vbs script in a hidden way:

System.Diagnostics.Process p = new System.Diagnostics.Process();

p.StartInfo = new System.Diagnostics.ProcessStartInfo("cscript");
p.StartInfo.Arguments = "C:\\test.vbs";

p.StartInfo.CreateNoWindow = true;
p.StartInfo.UseShellExecute = false;

p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;

p.OutputDataReceived += (proc, outLine) => LogMessage(outLine.Data);
p.ErrorDataReceived += (proc, outLine) => LogError(outLine.Data);

p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine ();

Tuesday, March 26, 2013

How to get tweets

I do not have twitter account yet and thus I was not thinking about the possibilities of integration it into applications. However, recently I was watching video about M# and I was really impressed about it. So I decided to try to get tweets.

Here is the simplest attempt:

public void LoadNewsLine(string username)
{
    WebClient twitter = new WebClient();

    twitter.DownloadStringCompleted += (sender, e) =>
        {
            XElement xmlTweets = XElement.Parse(e.Result);

            var message = from tweet in xmlTweets.Descendants("status")
                            select tweet.Element("text").Value;
        };
    twitter.DownloadStringAsync(new Uri(string.Format("http://api.twitter.com/1/statuses/user_timeline.xml?screen_name={0}", username)));
}


And you can get the information about the user with this request: "https://api.twitter.com/1/users/profile_image?screen_name=USERNAME". You can also add "&size=bigger" to request a bigger image :)

But if you need something more complex you should check tweetsharp In order to not to reinvent the wheel.

Tuesday, January 22, 2013

How to get patronymic from Active Directory

As there is no special field for patronymic in the Active Directory (AD) it is often filled in the Display Name field, while as surname of the user is in the Surname field and his name is in the Given Name field.
So, if you want to import users from the AD with full names and hierarchy, you might try to simply load names, surnames and everything else you need. For patronymics I suggest parse full names: split it by spaces, then remove the name and the surname and join the rest. It should be your patronymic. At least it might be, as administrators might put only reduced forms of a name into display name...
Overral, here is my code, which recursively loads users:

/// 
/// Class for imported users
/// 
public class DomainUser
{
    public string FullName;
    public string Name;
    public string Surname;
    public string Login;
    public string EMail;
    public System.Security.Principal.SecurityIdentifier SID;

    public bool IsOrgUnit;
    public bool IsChecked; //used later for treeview

    public List SubUsers;

    public override string ToString()
    {
        return FullName;
    }
}

public static List GetADUsers(DirectoryEntry root = null)
{
    DirectorySearcher searcher;
    if (root != null)
        searcher = new DirectorySearcher(root);
    else
        searcher = new DirectorySearcher();

    searcher.SearchScope = SearchScope.OneLevel;
    //get only organizational units (for hierarchy) and users-persons
    searcher.Filter = "(|(objectclass=organizationalunit)(&(objectclass=user)(objectcategory=person)))";
            
    List users = new List();

    ResultPropertyValueCollection vc;
    foreach (SearchResult result in searcher.FindAll())
    {
        vc = result.Properties["objectclass"];
        if (vc == null || vc.Count == 0) continue;

        DomainUser currentEntry = new DomainUser();
        currentEntry.IsChecked = true;

        //object class might contain "top" as first entry - so check its actual class with "Contains"
        if (vc.Contains("organizationalUnit"))
        {
            currentEntry.IsOrgUnit = true;

            vc = result.Properties["name"];
            if (vc != null && vc.Count > 0) currentEntry.FullName = vc[0].ToString();

            //as OU can have subusers - look into it:
            currentEntry.SubUsers = GetADUsers(result.GetDirectoryEntry());
        }
        else if (vc.Contains("user"))
        {
            currentEntry.IsOrgUnit = false;

            vc = result.Properties["displayName"];
            if (vc != null && vc.Count > 0) currentEntry.FullName= vc[0].ToString();

            vc = result.Properties["sn"];
            if (vc != null && vc.Count > 0) currentEntry.Surname= vc[0].ToString();

            vc = result.Properties["givenname"];
            if (vc != null && vc.Count > 0) currentEntry.Name = vc[0].ToString();

            vc = result.Properties["objectsid"];
            if (vc != null && vc.Count > 0) currentEntry.SID = new System.Security.Principal.SecurityIdentifier((byte[])vc[0], 0);

            vc = result.Properties["mail"];
            if (vc != null && vc.Count > 0) currentEntry.EMail = vc[0].ToString();

            vc = result.Properties["samAccountName"];
            if (vc != null && vc.Count > 0) currentEntry.Login = vc[0].ToString();
        }

        users.Add(currentEntry);
    }

    return users;
}

private void GetUserFullName(DomainUser user)
{
    //if that user has surname and name - we can try to get its patronymic
    if (!string.IsNullOrEmpty(user.Surname) && !string.IsNullOrEmpty(user.Name))
    {
        string name = user.Name.Trim();
        string surname = user.Surname.Trim();

        List fullNameParts = new List(user.FullName.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries));
        //if there is name and surname in display name
        if (fullNameParts.Remove(name) && fullNameParts.Remove(surname))
        {
            string patronymic = string.Join(" ", fullNameParts);
        }
    }
}

Wednesday, January 16, 2013

How to disable glue in Visio

Recently I was implementing text labels for shapes inside visio control. This task was not very hard - I had to add new shape for labels into our stencil, add action for specific shapes and drop labels with that action.
Here is the method to add action for shape:

class VisioUtility
{
    public static string GetCellValue(Shape shape, VisSectionIndices section, VisRowIndices row, VisCellIndices column)
    {
        Cell cell = GetCell(shape, section, row, column);
        return cell.Formula;
    }

    public static Cell GetCell(Shape shape,VisSectionIndices section,VisRowIndices row, VisCellIndices column)
 {
  return shape.get_CellsSRC((short)section,(short)row,(short)column);
  }

    public static void AddSectionRow(Shape shape, short sectionIndex, VisRowIndices rowIndex, VisRowTags rowTag)
    {
        shape.AddRow(sectionIndex, (short)rowIndex, (short)rowTag);
    }

    public static void SetCellValue(Shape shape, VisSectionIndices section, VisRowIndices row, VisCellIndices column, string value)
    {
        Cell cell = GetCell(shape, section, row, column);
        if (cell.Formula != value)
        {   //in case of UNDO...
            try
            {
                cell.FormulaU = value;
            }
            catch (System.Runtime.InteropServices.COMException ex) //7580 - workaround
            {
                //handle the exception
            }
        }
    }
}

class VisioForm
{
    public static void AddAction(Shape element, string actionMarker, string title, bool isSubMenu, bool beginGroup)
    {
        //Check if this menu item is already exists
        short newItemNumber = 0;
        while (element.get_CellsSRCExists((short)VisSectionIndices.visSectionAction, (short)(VisRowIndices.visRowAction + newItemNumber), (short)VisCellIndices.visActionAction, 0) != 0)
        {
            string currentTitle = VisioUtility.GetCellValue(element, VisSectionIndices.visSectionAction, VisRowIndices.visRowAction + newItemNumber, VisCellIndices.visActionMenu);

            if (VisioUtility.StripQuotes(currentTitle) == title)
                return;

            newItemNumber++;
        }

        //add new item as last one
        newItemNumber = element.get_RowCount((short)VisSectionIndices.visSectionAction);

        VisioUtility.AddSectionRow(element, (short)VisSectionIndices.visSectionAction, VisRowIndices.visRowAction + newItemNumber, (short)VisRowTags.visTagDefault);
        Cell cell = element.get_CellsSRC((short)VisSectionIndices.visSectionAction, (short)(VisRowIndices.visRowAction + newItemNumber), (short)VisCellIndices.visActionAction);
        //set action marker, so we can handle it after
        cell.FormulaU = string.Format("RUNADDONWARGS(\"QueueMarkerEvent\",\"{0}\")", actionMarker);

        //set title
        VisioUtility.SetCellValue(element, VisSectionIndices.visSectionAction, VisRowIndices.visRowAction + newItemNumber, VisCellIndices.visActionMenu, title);

        if (isSubMenu)
            VisioUtility.SetCellValue(element, VisSectionIndices.visSectionAction, VisRowIndices.visRowAction + newItemNumber, VisCellIndices.visActionFlyoutChild, "1");

        if (beginGroup)
            VisioUtility.SetCellValue(element, VisSectionIndices.visSectionAction, VisRowIndices.visRowAction + newItemNumber, VisCellIndices.visActionBeginGroup, "1");
    }
}

And I had to handle MarkerEvent for visio application
visioApp.MarkerEvent += new EApplication_MarkerEventEventHandler(visioApp_MarkerEvent);

The main problem I faced with this task is how to disable glue for those labels, as for visio document GlueSettings we have visGlueToGeometry. I asked for this question on visguy forum and got few interesting advices, however they were not what I was looking for. The most usefull advice was to create a group and set IsSnapTarget to False. I gave up on this path, since creating the group was too complex.


The solution turned to be very easy. There is cell that disables connect for geometry NoSnap, so simply put True there!