0

I'm coding in Microsoft Visual Studio, C#, WPF.

My main window (MainForm.cs) has BorderStyle=none and it should load several UserControls into a panel (that covers almost the entire window). All are in the same namespace. Some elements in these UserControls need to have a MouseDown event (in their respective designer files) that calls a function in the MainForm.cs (that function makes the mousedown'd element drag the entire window).

This is just an example. Other items in the UserControl will need to call different functions from the MainForm.cs into a EventHandler or a MouseEventHandler. Basically I need that an event will call (not sure if this is the right term in this context) a function from the main window.

I've seen many solutions for this, but none have worked (maybe because I didn't search with the correct terms, or maybe because I didn't implement them well in my code).

Some relevant code from the main window:

public partial class MainForm : Form
{
    private const int HTCAPTION = 0x2;
    private const int WM_NCLBUTTONDOWN = 0xA1;
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    public static extern bool ReleaseCapture();
    public void OnMousedown(object sender, MouseEventArgs e)
    {

        // Allow the form to be draggable
        if (e.Button == MouseButtons.Left)
        {
            ReleaseCapture();
            SendMessage(Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0);
        }
    }
}

... and some current code from a usercontrol designer file:

partial class CopperOrNonCopperSelection
{
    private void InitializeComponent()
    {
        // SelectCopperOrNonCopperLabel is a label in the usercontrol. There's more code here to customize this label.
        this.SelectCopperOrNonCopperLabel.MouseDown += new System.Windows.Forms.MouseEventHandler(((MainForm)Parent).OnMousedown);
    }
}

This code works in the MainForm designer file, when I want to apply the same function on a different item in MainForm.cs when MouseDown'd:

        this.ElementName.MouseDown += new System.Windows.Forms.MouseEventHandler((this.OnMousedown);

Obviously simply changing "this" to "MainForm" in the usercontrol designer file doesn't work. I've actually tried so many other options (and, to be honest, without understanding them) but I can't remember them off-hand.

Somehow the MouseDown event should do whatever in the OnMousedown function from MainForm. I've seen many users saying that this is bad practice, so I'm open to other suggestions (other than copy-pasting the function to the usercontrol).

With the current code mentioned above, I get an Delegate to an instance method cannot have null 'this'.' error on the ... (((MainForm)Parent).OnMousedown) code.

Itiel
  • 13
  • 4
  • `this` will reference the current control. `this.Parent` will reference the parent control of the current control. `this.parent.parent` will ... You need to find out which parent is the actual mainform and then you can actually do this: `(((MainForm)this.Parent.Parent).OnMousedown)` If you want to do this in a robust way you can get the instance of MainForm with a singelton or you search recursive through all parents. Dont forget to check for Parent == null – Sebastian Siemens May 23 '19 at 08:19
  • Thanks for the quick response! The Parent technique didn't work for some reason :( About your second suggestion, did you mean something like `MainForm test` in the usercontrol and then (test.OnMousedown)? – Itiel May 23 '19 at 10:06
  • Somehow this doesn't invoke the OnMousedown. Didn't you mean `Parent != null`? – Itiel May 23 '19 at 10:12

1 Answers1

0

I tried to understand your question. First, you said WPF. Your code shows Winforms.

My suggestion is to use singleton pattern. But this is not best practice.

My MainForm.cs looks like this:

public partial class MainForm : Form
{
    private const int HTCAPTION = 0x2;
    private const int WM_NCLBUTTONDOWN = 0xA1;
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    public static extern bool ReleaseCapture();
    public void OnMousedown(object sender, MouseEventArgs e)
    {

        // Allow the form to be draggable
        if (e.Button == MouseButtons.Left)
        {
            ReleaseCapture();
            SendMessage(Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0);
        }
    }

    public static MainForm Instance;

    public MainForm()
    {
        Instance = this;
        InitializeComponent();
    }
}

I have a public static property Instance which will be the reference of the MainForm, that I instantiate.

In my UserControl I use this Property to access Properties and Events of the instantiated MainForm class.

public partial class UserControl3 : UserControl
{
    public UserControl3()
    {
        InitializeComponent();
        this.MouseDown += new MouseEventHandler(MainForm.Instance.OnMousedown);
        this.label1.MouseDown += new MouseEventHandler(MainForm.Instance.OnMousedown);
    }
}

Another way to get the Parent Control recursive you can use this method:

public static Control GetParentRecursive<T>(Control myControl)
{
    Control rControl = null;

    if (myControl.Parent != null)
    {
        if (myControl.Parent.GetType() == typeof(T))
        {
            rControl = myControl.Parent;
        }
        else
        {
            rControl = GetParentRecursive<T>(myControl.Parent);
        }
    }
    else
    {
        rControl = null;
    }

    return rControl;
}

Call this method like this:

GetParentRecursive<MainForm>(this);
Sebastian Siemens
  • 1,679
  • 13
  • 19
  • Thanks! The `MainForm.Instance.OnMousedown` solution worked for me. I almost pulled my hair out! Sorry about the winforms vs. WPF, I though they were one and the same. :( As you (and others) have said, this is bad practice. But why? Is it from RAM usage point of view (MainForm being utilized twice or something like that), or something else? What would you do in my stead? – Itiel May 23 '19 at 17:03
  • One more thing I don't understand is what the `Parent != null` is all about. Why do I need to check that? – Itiel May 23 '19 at 17:07
  • `Parent != null` => the problem is, that a control could be the top most parent. As the top most parent has no parent, parent property will be null. Second situation, when you instantiate a control, it has no parent until it is added to another control. In this two situations, it is possible, that the parent property is null. – Sebastian Siemens May 24 '19 at 07:18
  • @Itiel `Singleton` => you have to keep in mind, that a static property can be accessed in a multi threaded application. You could create many instances of `MainForm` and thus the `Instance` property will be overwritten each time. For more information look at this question: https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons – Sebastian Siemens May 24 '19 at 07:21
  • Thanks Sebastian, I think you've answered all of my questions. Checking for Parent != null is not necessary in my implementation (at least, the app does not crash without this check). My application does not use multi-threading so I don't think there's a problem with a Singleton. I'll just Dispose of it when I'm done with it. I'm aware there are more aspects to this aside from memory management, but as a beginner I think (and hope) this is good enough. – Itiel Jun 02 '19 at 18:19