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.