Pages

Tuesday, December 25, 2012

Drag & Drop Tab Control

Recently I had to implement drag and drop tab control, so users would be able to reorder tab pages inside control by simple drag-n-drop. As any other developer I started to google and found this solution: CodeProject. It seemed quite nice to me, so I used it.
In our application we are using DevExpress controls and XtraTabControl as well, so my TabControl was inherited from DevExpress.XtraTab.XtraTabControl. It worked as expected, but little later we have found that on Windows 7 there is a bug. If someone would try to drag second tab into the first position there will be error:

Object reference not set to an instance of an object. System.NullReferenceException at DevExpress.XtraTab.Drawing.SkinTabPageObjectInfo.CalcColor(BaseTabPageViewInfo pInfo) at DevExpress.XtraTab.Drawing.SkinTabPageObjectInfo..ctor(SkinElement element, BaseTabPageViewInfo pInfo) at DevExpress.XtraTab.ViewInfo.SkinTabHeaderViewInfo.UpdatePageInfo(BaseTabPageViewInfo pInfo) at DevExpress.XtraTab.Drawing.SkinTabPainter.DrawHeaderPage(TabDrawArgs e, BaseTabRowViewInfo row, BaseTabPageViewInfo pInfo) at DevExpress.XtraTab.Drawing.BaseTabPainter.DrawPage(TabDrawArgs e, BaseTabRowViewInfo rowInfo, BaseTabPageViewInfo pInfo) at DevExpress.XtraTab.Drawing.BaseTabPainter.DrawHeaderRow(TabDrawArgs e, BaseTabRowViewInfo rowInfo) at DevExpress.XtraTab.Drawing.BaseTabPainter.DrawHeader(TabDrawArgs e) at DevExpress.XtraTab.Drawing.BaseTabPainter.DrawForeground(TabDrawArgs e) at DevExpress.XtraTab.Drawing.BaseTabPainter.Draw(TabDrawArgs e) at DevExpress.XtraTab.XtraTabControl.OnPaint(PaintEventArgs e) at System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer) at System.Windows.Forms.Control.WmPaint(Message& m) at System.Windows.Forms.Control.WndProc(Message& m) at DevExpress.Utils.Controls.ControlBase.WndProc(Message& m) at DevExpress.XtraTab.XtraTabControl.WndProc(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m) at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

And the entire tab control becomes crossed out with the two diagonal red lines. Terrible. But much more interesting, that I was not able to reproduce this error on Windows XP!
However, I found the solution. This implementation has too complex way to reorder tabs, so here is my version of OnDragOver:

protected override void OnDragOver(System.Windows.Forms.DragEventArgs e)
{
    base.OnDragOver(e);

    Point pt = new Point(e.X, e.Y);
    //We need client coordinates.
    pt = PointToClient(pt);

    //Get the tab we are hovering over.
    XtraTabPage hover_tab = null;
    for (int i = 0; i < TabPages.Count; i++)
    {
        DevExpress.XtraTab.ViewInfo.XtraTabHitInfo info = this.CalcHitInfo(pt);
        if (info.HitTest == DevExpress.XtraTab.ViewInfo.XtraTabHitTest.PageHeader)
        {
            hover_tab = info.Page as XtraTabPage;
            break;
        }
    }

    //Make sure we are on a tab.
    if (hover_tab != null)
    {
        //Make sure there is a XtraTabPage being dragged.
        if (e.Data.GetDataPresent(typeof(XtraTabPage)))
        {
            e.Effect = DragDropEffects.Move;
            XtraTabPage drag_tab = (XtraTabPage)e.Data.GetData(typeof(XtraTabPage));

            if (drag_tab.Parent != this) //if we are trying to drag "aliens" tab - then stop!
            {
                e.Effect = DragDropEffects.None;
                return;
            }

            int item_drag_index = FindIndex(drag_tab);
            int drop_location_index = FindIndex(hover_tab);

            //Don't do anything if we are hovering over ourself.
            if (item_drag_index != drop_location_index)
            {
                //simply set child index!
                TabPages.SetChildIndex(hover_tab, item_drag_index);
                TabPages.SetChildIndex(drag_tab, drop_location_index);

                if (AfterDragNDrop != null) //there can be delegates for after Drag-N-Drop
                    AfterDragNDrop();

                //Make sure the drag tab is selected.
                SelectedTab = drag_tab;
            }
        }
    }
    else
    {
        e.Effect = DragDropEffects.None;
    }
}

Wednesday, December 19, 2012

Default parameters in delegates and reflection

Recently I have refactored a method. It was created in C# 2.0 and it was overriden many times to support default parameters, something like this:

public void Foo()
{
    Foo(0);
}

public void Foo(int arg1)
{
    Foo(arg1, "");
}

public void Foo(int arg1, string arg2)
{
    //finally, do something
}

But we have migrated to C# 4.0, so it is better to use actual default parameters, like this:

public void Foo(int arg1 = 0, string arg2 = "")
{
    //do something
}

However there are 2 questions. How it will work with delegates and how it will work with reflections. The answers are simple. You have to declare delegate with default parameter and for reflection you can use Type.Missing object. Here are 2 examples:

About delegates:
void Foo(int arg1 = 0, string arg2 = "")
{
    //do something
}

void Foo2(int arg1, string arg2)
{
    //do something
}

void FooError(int arg1)
{
    //do something
}

delegate void FooDelegate(int arg1 = 0, string arg2 = "");

void SomeWhere()
{
    //you can declare delegates with default parameters:
    FooDelegate method = Foo;
            
    //and invoke them:
    method();
    method(arg1: 10);
    method(arg2: "string value");

    //and you can even use functions without defaut parameters:
    FooDelegate method2 = Foo2;

    method2();
    method2(1, "string");

    //but you cannot use method with different number of arguments:
    FooDelegate method3 = FooError; //Here is error!
    method3();
}

And about reflection:
void SomeWhere()
{
    Type myType = typeof(MyClass); //for simplicity I will get Type like this
    var myObj = Activator.CreateInstance(myType); //create instance of the class
    var myMethodInfo = myType.GetMethod("Foo"); //and get method info from Type

    // now we can invoke that method with Type.Missing parameter for default value
    myMethodInfo.Invoke(myObj, new object[] { Type.Missing, Type.Missing });
    myMethodInfo.Invoke(myObj, new object[] { 10, Type.Missing });
    //however the order of the parameters must be correct!
    myMethodInfo.Invoke(myObj, new object[] { Type.Missing, "string" });

    myMethodInfo.Invoke(myObj, new object[] { "string", Type.Missing }); //this will throw an error!
}

Saturday, December 8, 2012

Composite pattern for testing applications

It is very common task to write some sort of testing application. It can be both an educational task and a commectial product. And the use of the composite pattern can dramatically simplify this task (of course if you are not making one-level tests). Composition pattern allows you to work with case studie as with any other kind of questions. You can read about this pattern here.
Here is my simple example of this pattern:

Classes for questions:
public abstract class TestTask
{
    public TestTask(string text)
    {
        answers = new List<TaskAnswer>();
        TaskText = text;
    }

    //abstract methods, common for every existing task
    public abstract void AddSubElement(TestTask element);
    public abstract void RemoveSubElement(TestTask element);
    public abstract bool CheckTask();
        
    //methods and properties for answers
    private List<TaskAnswer> answers;
    public string TaskText { get; set; }

    public void AddAnswer(params KeyValuePair<string, bool>[] newAnswers)
    {
        foreach (var answer in newAnswers)
        {
            answers.Add(new TaskAnswer(answer.Key, answer.Value));    
        }
    }
    public void RemoveAnswer(int number)
    {
        answers.RemoveAt(number);
    }
}

public class SingleChoiseTask: TestTask
{
    public SingleChoiseTask(string text) : base(text) {}

    //property for selected answer
    public TaskAnswer SelectedAnswer { get; set; }

    public override bool CheckTask()
    { //for single choise question just check if selected answer is correct
        return SelectedAnswer != null && SelectedAnswer.IsCorrect;
    }

    //single choise question cannot have any children
    public override void AddSubElement(TestTask element)
    {
        throw new NotImplementedException();
    }

    public override void RemoveSubElement(TestTask element)
    {
        throw new NotImplementedException();
    }
}

public class MultipleChoiseTask : TestTask
{
    public MultipleChoiseTask(string text): base(text)
    {
        SelectedAnswers = new List<TaskAnswer>();
    }

    //List for selected answers
    public List<TaskAnswer> SelectedAnswers;
    public override bool CheckTask()
    { //for multiple choise question we have to check that every selected answer is correct
        return SelectedAnswers.Count > 0 && SelectedAnswers.TrueForAll(answer => answer.IsCorrect);
    }

    // multiple choise question cannot have any children
    public override void AddSubElement(TestTask element)
    {
        throw new NotImplementedException();
    }

    public override void RemoveSubElement(TestTask element)
    {
        throw new NotImplementedException();
    }
}

public class  CaseStudieTask: TestTask
{
    public CaseStudieTask(string text): base(text)
    {
        SubTasks = new List<TestTask>();
    }

    //List of childrens
    private List<TestTask> SubTasks;

    public override void AddSubElement(TestTask element)
    {
        SubTasks.Add(element);
    }
    public override void RemoveSubElement(TestTask element)
    {
        SubTasks.Remove(element);
    }

    public override bool CheckTask()
    { // for case studie we have to check that every sub question is correct
        return SubTasks.TrueForAll(el => el.CheckTask());
    }
}
Class for answers:
public class TaskAnswer
{
    public string AnswerText { get; set; }
    public bool IsCorrect { get; set; }

    public TaskAnswer(string text, bool isCorrect)
    {
        AnswerText = text;
        IsCorrect = isCorrect;
    }
}
And example of usage:
public void InitializeTest()
{
    //Create container
    TestTask test = new CaseStudieTask("First test");
            
    //Initialise sample questions
    var firstQuestion = new SingleChoiseTask("First question");
    firstQuestion.AddAnswer(new KeyValuePair<string, bool>("correct answer", true),
                            new KeyValuePair<string, bool>("incorrect answer", false));

    var secondQuestion = new MultipleChoiseTask("Second question");
    secondQuestion.AddAnswer(new KeyValuePair<string, bool>("correct answer 1", true),
                                new KeyValuePair<string, bool>("correct answer 2", true));

    var thirdQuestion = new CaseStudieTask("Case studie");
    var firstSubQuestion = new SingleChoiseTask("First question in case studie");
    firstSubQuestion.AddAnswer(new KeyValuePair<string, bool>("correct answer", true),
                            new KeyValuePair<string, bool>("incorrect answer", false));
    var secondSubQuestion = new SingleChoiseTask("Second question in case studie");
    secondSubQuestion.AddAnswer(new KeyValuePair<string, bool>("correct answer", true),
                            new KeyValuePair<string, bool>("incorrect answer", false));
    thirdQuestion.AddSubElement(firstSubQuestion);
    thirdQuestion.AddSubElement(secondSubQuestion);

    // And populate test
    test.AddSubElement(firstQuestion);
    test.AddSubElement(secondQuestion);
    test.AddSubElement(thirdQuestion);
}
Of course this example is not full. There is no way to navigate throught test, no binding for the answers and no scores (user can pass the test only if he scores 100%).
And I would add singleton pattern for test (and this class would calculate current points, watch for current question etc.). If you are interested I can show more complex example.

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();
    }
}