Pages

Thursday, November 29, 2012

Multiselect tree view

Recently I needed to implement multiselect tree view. Knowing that there is nothing new under the sun I googled for solution and found this article. At first glance it worked as expected, however later I had to improve it.
  • it does not support editing of text nodes;
  • added property for turning off multiselect;
  • removed built-in tooltips;
  • enabled node selection with clicks in a row, not only on text and icon.
So here are my changes:

// Disable built-in tooltips
protected override CreateParams CreateParams 
{
    get
    {
        CreateParams parms = base.CreateParams;
        parms.Style |= 0x80;  // Turn on TVS_NOTOOLTIPS 
        return parms;
    }
}

// Switch to enable or disable multiselect
private bool _multiSelectEnabled;
public bool MultiSelectEnabled 
{ 
    get { return _multiSelectEnabled; }
    set
    {
        _multiSelectEnabled = value;
        //reset selection
        if (!_multiSelectEnabled && SelectedNodes.Count > 0) 
        SelectedNode = SelectedNodes[0];
    }
}

private List m_SelectedNodes;
public List SelectedNodes
{
    get { return m_SelectedNodes; }
    set
    {
        ClearSelectedNodes();
        if (MultiSelectEnabled)
        {
            if (value != null)
            {
                foreach (TreeNode node in value)
                {
                    ToggleNode(node, true);
                }
            }
        }
        else
        {
            if (value.Count > 0)
            {
                m_SelectedNodes.Clear();
                ToggleNode(value[0], true);
            }
        }
    }
}

// if user clicks on node, we will check if we need to edit nodes text
private TreeNode _clickedNode;
protected override void OnMouseDown(MouseEventArgs e)
{
    // If the user clicks on a node that was not previously selected, select it now.
    try
    {
        base.SelectedNode = null;

        TreeNode node = this.GetNodeAt(e.Location);
        if (node != null)
        {
            if (ModifierKeys == Keys.None && (m_SelectedNodes.Contains(node)))
            {
                _clickedNode = node;
            }
            else
            {
                if (MultiSelectEnabled) //if multiselect enabled - SelectNode
                {
                    SelectNode(node);
                }
                else //else select single node
                {
                    SelectSingleNode(node);
                }
            }
        }

        base.OnMouseDown(e);
    }
    catch (Exception ex)
    {
        HandleException(ex);
    }
}

protected override void OnMouseUp(MouseEventArgs e)
{
    try
    {
        var node = this.GetNodeAt(e.Location);
        if (node != null &&
            ModifierKeys == Keys.None && m_SelectedNodes.Contains(node))
        {
            if (MultiSelectEnabled)
            {
                SelectNode(node);
            }
            else
            {
                SelectSingleNode(node);
            }

            int leftBound = node.Bounds.X;
            int rightBound = node.Bounds.Right;
            if (_clickedNode == node && e.Location.X > leftBound && e.Location.X < rightBound)
            {
                _clickedNode.BeginEdit(); // single click on node - we can rename it
            }
        }

        _clickedNode = null; //clear clicked node

        base.OnMouseUp(e);
    }
    catch (Exception ex)
    {
        HandleException(ex);
    }
}

Thursday, November 22, 2012

Dirty tree view

Recently I have noticed strange behaviour with WinForms TreeView. I have worked on multiselect tree view, which is based on simple treeView and noticed this:
After clearing long node text there is blue rectangle - right as this text was before deleting. It is not very bad, but it seems like a bug. I have checked all my code, but was not able to find the reason for this. At the end I tried to reproduce this behavior in the prototype, and I have found that problem not in my multiselect tree, but in the .Net.

I have found two solutions for this problem:
  • You can enable FullRowSelect in your tree. As for me, it looks much more stylish than simple selecting, so I choose this option;
  • Or you can disable visual styles in your application. To disable visual styles simply comment out Application.EnableVisualStyles(); in your main() function.

Tuesday, November 20, 2012

Healthy coding

What about healthy coding? As a software developers we dont have to live a healthy live. Most of our time we are sitting in front of the monitor while our fingers are flying over keyboard as a butterflies. (So romantic!) But is it really that good? Or we have to think about our body too?

Of course, there are plenty of safety rules. They are all correct and reasonable, but personally, I tend to forget about them as soon as I start to work... So my posture is far from ideal and often my nose is too close to the monitor! I even have dinner in front of the computer! On the other hand, I make short breaks every 2-3 hours (cannot work witout coffee...), also, I often go to another room to analysts to discuss current tasks.

But in general, these rules are made to reduce the load on our main tool - the brain. We can distract from problems while breaks, exercise improves blood circulation. Other tools that important for developers are eyes and hands with fingers, so it is very important to use ergonomic mouse and keyboard and wide modern display.

And now about relaxations! Many people prefer to watch TV or play computer games after hard day. I also like to play games. I used to play computer games until the night, but now I prefer board games in good company. You cannot argue, that live conversation is much better! But those are types of intelligent rest, and simply switching the direction of thought is not enought sometimes. You might want to switch your brain off time to time. I think the best opportunity to stay in fit is participation in tourism hikes! I really love hiking!

Since I cannot take part in the campaigns as often as I would liked, I found an alternative. Every day I go to work, I walk through the park. It takes about 40 minutes to get to work, but it totaly worth it! I can breathe semi-clean air and enjoy beautiful views. So I heartily suggest you to find a similar route to work. It allows you to prepare for working day and often during a walk most graceful solutions are born in the head!





Sunday, November 11, 2012

How to get unique hardware id

If you write something like licensing for your application, you definitely will think to get the unique hardware id to bind your key to PC. So overral your scheme would look like this:

  • Generation of the code on the clients PC;
  • Getting that code to license generator;
  • Some manupilations with that code;
  • Sending resulting key to the client;
  • Check license key with every run of the clients application.

The simples implementation would be generation of the code based on hardware. It can be easily done with WMI (Windows management instrumentation). The simplest method would be to use processor id, like this:

public static string GetProcessorId()
{
    string processorID;
    var searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_Processor");

    foreach (ManagementObject queryObj in searcher.Get())
    {
        processorID = queryObj["ProcessorId"]).ToString();
    }
    return processorID;
}

It would give you something like this: "BFEBFBFF000206A7".
There are also other options from WMI. You could use:

Dictionary<string, string> IDs = new Dictionary<string, string>();

//MotherBoard
searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM CIM_Card");
foreach (ManagementObject queryObj in searcher.Get())
    IDs.Add("CardID", queryObj["SerialNumber"].ToString());

//OS
searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM CIM_OperatingSystem");
foreach (ManagementObject queryObj in searcher.Get())
    IDs.Add("OSSerialNumber", queryObj["SerialNumber"].ToString());

//Keyboard. I am not really sure if is is a good idea - to bind to keyboard...
searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM CIM_KeyBoard");
foreach (ManagementObject queryObj in searcher.Get())
    IDs.Add("KeyBoardID", queryObj["DeviceId"].ToString());

//Mouse. It is also not the most stable part of the hardware...
searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_PointingDevice");
foreach (ManagementObject queryObj in searcher.Get())
    IDs.Add("MouseID", queryObj["DeviceID"].ToString());

//SoundCard
searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_SoundDevice");
foreach (ManagementObject queryObj in searcher.Get())
    IDs.Add("SoundCardID", queryObj["DeviceID"].ToString());

//UUID
searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT UUID FROM Win32_ComputerSystemProduct");
foreach (ManagementObject queryObj in searcher.Get())
    IDs.Add("UUID", queryObj["UUID"].ToString());

So there are plenty of options and if you want to bind not only to a single component, you can use hashing, for example:

private static string GetUniqueHardwaeId()
{
  var sb = new StringBuilder();

  var searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_Processor");
  foreach (ManagementObject queryObj in searcher.Get())
  {
    sb.Append(queryObj["NumberOfCores"]);
    sb.Append(queryObj["ProcessorId"]);
    sb.Append(queryObj["Name"]);
    sb.Append(queryObj["SocketDesignation"]);
  }

  var bytes = Encoding.UTF8.GetBytes(sb.ToString());
  var sha = new System.Security.Cryptography.SHA256Managed();

  byte[] hash = sha.ComputeHash(bytes);
  return BitConverter.ToString(hash);
}

Tuesday, November 6, 2012

An invalid character was found in the mail header

I have faced System.FormatException recently and want to share my solution.
I developed email functionality for multiple projects and never thought I would meet unexpected difficulties at this time. But this exception was waiting for me right at my home PC. I am using Russian Windows 7 64x and this is the root of all evil. Under .Net 2.0 System.Net.Mail somehow cannot create even the simplest mails! Changing to modern .Net is not an option because of system requirements.
So there are only two options left.
First of all I could change system language: Control panel->Regional and Language->Administrative tab->Change system local. It would work, but what about my users? Other option is to remember old deprecated System.Web.Mail.
Here is simplest code snippet:

using net = System.Net.Mail;
using web = System.Web.Mail;
...
static void Main(string[] args)
{
    web.MailMessage mail = new web.MailMessage();
    mail.To = "MAILTO@gmail.com";
    mail.From = "MAILFROM@gmail.com";
    mail.Subject = "this is a test email.";
    mail.Body = "this is my test email body";

    mail.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusing", "2");
    mail.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpusessl", "true");

    mail.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate", "1");
    mail.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusername", "YOURMAIL@gmail.com"); //your username here
    mail.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendpassword", "PASSWORD"); //your password here
    mail.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserverport", 465);
    web.SmtpMail.SmtpServer = "smtp.gmail.com"; //your real server goes here

    web.SmtpMail.Send(mail);
    
    return; //Just as example of previous attempt

    net.SmtpClient smtp = new net.SmtpClient();
    smtp.UseDefaultCredentials = false;
    smtp.Credentials = new System.Net.NetworkCredential("YOURMAIL@gmail.com", "PASSWORD");
    smtp.Port = 587;
    smtp.Host = "smtp.gmail.com";
    smtp.EnableSsl = true;

    var mailNet = new net.MailMessage("MAILTO@gmail.com",
                                      "MAILFROM@gmail.com",
                                      "SUBJECT",
                                      "MAIL BODY");

    smtp.Send(mailNet); //Here we got a error... As you see I have not used any non-unicode characters...
}
And there is one more interesting thing. Gmail supports two ports for mails - 465 and 587. For Web.Mail you can use 465 port as Web.Mail using Pop protocol by default and for Net.Mail you can use 587 port as it using IMAP protocol.

Tuesday, October 30, 2012

Extensibility of the desktop applications

Application extensibility question arises in almost every project. For web-applications, it can be, for example, horizontal and vertical scalability, and in terms of desktop applications, it may be a new version or the use of plug-ins.

Releasing of a new version is simple. When there are enough extra stuff, you just build your application, assign a new version number and write release notes. Deploy and enjoy!

Plugins are much more complex. First of all, you have to foresee what features will be able to be extensible through plugins. The main application must be able to register a necessary plugins and run them at the right time. Cooperation of two plugins is also regulated by the main application.

From .Net 4.0 the task of developing and supporting plugins become much easier, as there was presented MEF - Managed Extensibility Framework. I will describe the work with it little later, and now I would like to concentrate on the simplest example of a plugin development in .Net 2.0

In .Net 2.0 it can be done throught reflection. For simplicity it is better to implement some common inteface in your plugins, and use that interface in the main application. So, your programm should know where to look for plugins and how to run them.
Here is simple example: application that do nothing except for running plugins, user can run every plugin with buttons from main application.

PluginHost - simple win forms application. It does not have any control and there is only 2 handlers - Form_Load and Button_Click
private void MainForm_Load(object sender, EventArgs e)
{
    //Search for dlls
    string pathToAssembly = System.Reflection.Assembly.GetExecutingAssembly().Location;
    pathToAssembly = Path.GetDirectoryName(pathToAssembly) + "\\plugins";

    int plCount = 0; //number of correct plugins
    foreach (var file in Directory.GetFiles(pathToAssembly, "*.dll", SearchOption.TopDirectoryOnly))
    {
        var pluginModule = System.Reflection.Assembly.LoadFrom(file); //loading assembly
        var types = pluginModule.GetTypes(); //getting all the types inside it
        foreach (var type in types)
        if (type.GetInterface("IMyPlugin") != null) //if we found correct type
        { //then lets add button for this plugin
            Button newButton = new Button()
            {
                Size = new System.Drawing.Size(100, 25),
                Location = new Point(10, 10 + 25 * plCount),
                Text = Path.GetFileName(file),
                Tag = file
            };
            plCount++;
            newButton.Click += pluginButton_Click; //handler for plugin execution
            this.Controls.Add(newButton);
        }
    }
}

void pluginButton_Click(object sender, EventArgs e)
{
    //once again - load assembly
    var pluginModule = System.Reflection.Assembly.LoadFrom((sender as Button).Tag.ToString());
    var types = pluginModule.GetTypes();
    foreach (var type in types) //find interface
    if (type.GetInterface("IMyPlugin") != null)
    {
        //create instance of that class
        var pluginEntry = Activator.CreateInstance(type, null);
        //and run entry method
        System.Reflection.MethodInfo methodInfo = type.GetMethod("DoSomething");
        methodInfo.Invoke(pluginEntry, null);
    }
}
As you can see I am assuming that my plugins implement IMyPlugin interface, and there is DoSomething() method (declared in that interface). All plugins must be located in plugins directory near main executable.

MyPlugin is a dll (class library). It has interface declaration
public interface IMyPlugin
{
    void DoSomething();
}
And another form with one button that joyfully shouts of her working capacity
public partial class AdditionalForm : Form, IMyPlugin
{
    public AdditionalForm()
    {
        InitializeComponent();
    }

    private void btnShow_Click(object sender, EventArgs e)
    {
        MessageBox.Show("I am plugin!");
    }

    public void DoSomething()
    {
        this.ShowDialog();
    }
}