Click here to Skip to main content
Click here to Skip to main content

Many Questions Answered at Once — Collaboration between Windows Forms or WPF Windows

, 26 Mar 2015 CPOL
Rate this:
Please Sign up or sign in to vote.
Popular question on Forms collaboration, also answered for WPF
  1. Motivation
  2. The Idea
  3. Example of Collaboration
  4. Sample Solution for System.Windows.Forms
  5. Sample Solution for WPF
  6. Some Notes on Other Solutions
  7. Is the Collaboration Good or Evil?
  8. Conclusions

1 Motivation

This simple article is supposed to be one of the long-planned "Many Questions Answered at Once" series, that is, its main purpose is the use for answering questions in the CodeProject's Questions & Answers forum. I prepared the code for the first article a long time ago, to answer different questions at once, but still could not find time to complete it. This time, when I found that the question is not only popular but requires more detailed explanation, I decided to answer just one kind of question (depending how you count them, with WPF it can be counted as two).

The question is very trivial and mainly comes from the beginners. Everything is reduced to the following situation. One has two forms with control X in one and control Y in another. The user manipulates with first form, so something happens with X: selection is changed, button is pressed (the some other control is also involved, say, its value is retrieved). The questions are: how to reflect to these changes in Y: to add data in ListBox, ListView, GridView, copy some data from X to Y or from Y to X, find and select some item, show some data, anything like that. The questions are usually asked for System.Windows.Forms forms, sometimes for WPF windows.

2 The Idea

People published many solutions for this "problem", but none of them was really robust or comprehensive.

If the application is simple enough, the solution could be as simple as declaring of some internal property in one form and passing a reference to the instance of one form to the instance of another form. For more complex projects, such violation of strictly encapsulated style and loose coupling could add up the accidental complexity of the code and invite mistakes, so the well-encapsulated solution would be preferable.

Please see:
http://en.wikipedia.org/wiki/Accidental_complexity,
http://en.wikipedia.org/wiki/Loose_coupling.

Often, it was advised to use delegates or events, but this is just a distraction from the main issue: how forms should be make known to each other.

So, what to do for more complex application which needs to be neat? I believe the approach should be the most general. As form classes are isolated from each other by default, their means of communications should be abstracted from the form detail, and, as the form types always have some base classes, the only mean of abstraction is the use of interface types. One form implements some interface known to both forms. The reference to one form is passed to another form in the form of interface reference. This way, one form has no access to another form beyond the interface, which separates concerns and makes collaboration perfectly safe.

See also: http://en.wikipedia.org/wiki/Separation_of_concerns.

This is one feature which highly facilitates this: partial class declaration. In each separate partial declaration any base types are not required to be repeated in the inheritance list of each partial declaration. For example, one form file is auto-generated; and your forms inherits from System.Window.Forms.Form. It is not repeated in the other partial declaration of the same form type in the other file. Then the developer can create another partial declaration, with only one element of the inheritance list, the interface, and implement the interface in this part. It will separate the aspects of the interface from other code, improving maintainability.

3 Example of Collaboration

For example, let's say we want to add the content of some TextBox to some visual data container in another form or window. The form calling the method of such mechanism should not be aware of the nature of controls involved, it just adds some text using some Add(string) method. The detail of presentation of this accumulated data can be different: this is the implementation detail of the second form/window. We can add data to some ListBox or some instances of TextBox added to some panel. How to implement it?

4 Sample Solution for System.Windows.Forms

First, let's define the interface. In our example, it can be very simple:

internal interface ICollaboration {
    void Add(string text);
} //interface ICollaboration

Let's implement the presentation of added strings in the form of FlowLayoutPanel, which will collect read-only instances of TextBox control, which allows the user to see distinct instances of strings, even if they are multi-line texts.

I'll show all the code in without using any designers or XAML. Such code is much shorter, more elegant and easy for understanding and explanations. Please see the downloadable sample code which comes with the library. I show to the code used in the article in one file. In practice, all partial declarations can be put in separate file, as it is intended. I also include variants of projects written with the designer and XAML.

This is the implementation:

partial class ShowForm : ICollaboration {
    FlowLayoutPanel panel = new FlowLayoutPanel();
    void ICollaboration.Add(string text) {
        Show();
        TextBox textBox = new TextBox();
        textBox.Text = text;
        textBox.ReadOnly = true;
        textBox.Multiline = true;
        textBox.ScrollBars = ScrollBars.Both;
        textBox.Height *= 4;
        panel.Controls.Add(textBox);
        panel.ScrollControlIntoView(textBox);
        textBox.Focus();
    } //ICollaboration.Add
} //class ShowForm

The other partial declaration of the same class shows just the setup for the form:

partial class ShowForm : Form {
    internal ShowForm() {
        Text = DefinitionSet.showWindow;
        panel.Dock = DockStyle.Fill;
        panel.Parent = this;
        panel.AutoScroll = true;
        this.ShowInTaskbar = false;
        this.FormClosed += (sender, eventArgs) => { Application.Exit(); };
    } //ShowForm
} //class ShowForm

Note that the interface ICollaboration is shown in the inheritance list only of one of the parts, where it is implemented, and the base class is shown only in another part. Both members of the inheritance list are shown only once; this is a really decent style of writing partial declarations.

How this interface can be used in the other form? I put it as the last member of the form class, the property ICollaboration CollaborationForm. This is the complete class definition:

class MainForm : Form {
    internal MainForm() {
        Text = DefinitionSet.mainWindow;
        this.Padding = new Padding(4);
        Panel fill = new Panel();
        fill.TabIndex = 1;
        fill.Dock = DockStyle.Fill;
        TextBox textBox = new TextBox();
        textBox.TabIndex = 1;
        textBox.Multiline = true;
        textBox.Dock = DockStyle.Fill;
        textBox.Parent = fill;
        Panel bottom = new Panel();
        bottom.TabIndex = 2;
        bottom.Dock = DockStyle.Bottom;
        Button button = new Button();
        bottom.Height = button.Height * 2;
        button.Dock = DockStyle.Left;
        button.Parent = bottom;
        button.Text = DefinitionSet.button;
        bottom.Padding = new Padding(0, 4, 0, 0);
        Controls.Add(fill);
        Controls.Add(bottom);
        button.Click += (sender, eventArgs) => {
            if (CollaborationForm != null)
                CollaborationForm.Add(textBox.Text); // it makes it a closure
        }; //button.Click
        textBox.TextChanged += (sender, eventsArgs) => {
            button.Enabled = !string.IsNullOrEmpty(textBox.Text);
        }; //textBox.TextChanged
        button.Enabled = false;
    } //MainForm
    internal ICollaboration CollaborationForm { get; set; }
} //class MainForm

This class is called "MainForm" only because the instance of this form is used as the main form in the entry-point method, but actually it has no significance. One form is aware of another. It can have only the interface reference representing another form, without access to any form detail, which provides better encapsulation. One form calls the method(s) of another form as the interface method, through the interface reference.

Also, it is absolutely irrelevant to the direction of passing data. One form calls the method of another form. This method can send data to another form or receive data, or both; other method can do anything else. I may involve any data or not. As to the other aspects, such as using events or delegates, they also can be used through the interface: one form can add a handler to the invocation list of some event of the other form, set or get some delegate instance, read or set some property, and so on.

In our examples, one form holds an interface reference to another form, so one form "knows" another one, and another form remains agnostic to the first one. In other cases, the forms can implement two different interfaces and exchange interface references, thus "knowing" each other, but the interfaces would remain agnostic to the forms themselves and form identities.

Now, we need to establish this relationship between two instances of two form classes. It can be done at any time in any place of code, but the simplest way to demonstrate it would be to implement it in the entry-point of the application:

class EntryPoint {
    static void Main() {
        MainForm main = new MainForm();
        ShowForm secondForm = new ShowForm();
        main.CollaborationForm = secondForm;
        secondForm.Owner = main;
        Application.Run(main);
    } //void Main
} //class EntryPoint

Again, we made a "calling form" the main form of the application, but we could do the opposite. It just does not matter.

Actually, the whole application code is fully shown in this article section, except the static class DefinitionSet, which is simply a set of definitions of three strings used in the UI (texts on the button and both forms).

5 Sample Solution for WPF

The solution for WPF is only slightly different from that of Forms. Due to better flexibility of the WPF content model, ListBox can be used as a container for multi-line strings, showing boundaries between them by encapsulating each one in a separate instance of TextBlock each placed inside separate Border of appropriate style and Margins, used to separate those strings. This is the implementation of ICollaboration:

partial class ShowWindow : ICollaboration {
    ListBox listBox = new ListBox();
    void ICollaboration.Add(string text) {
        Border border = new Border();
        border.BorderThickness = new Thickness(1);
        border.BorderBrush = System.Windows.Media.Brushes.Black;
        border.Padding = new Thickness(4);
        border.Margin = new Thickness(1);
        TextBlock textBlock = new TextBlock();
        textBlock.Text = text;
        border.Child = textBlock;
        this.listBox.Items.Add(border);
        this.listBox.ScrollIntoView(border);
        this.listBox.SelectedIndex = this.listBox.Items.Count - 1;
    } //ICollaboration.Add
} //class ShowWindow

The implementation of the "calling window", which is the main window in the application example (again, it does not matter), is also very similar:

class MainWindow : Window {
    internal MainWindow() {
        Title = DefinitionSet.mainWindow;
        this.Padding = new Thickness(4);
        DockPanel panel = new DockPanel();
        panel.LastChildFill = true;
        Label label = new Label();
        label.Content = DefinitionSet.label;
        TextBox textBox = new TextBox();
        textBox.AcceptsReturn = true;
        label.Target = textBox;
        Button button = new Button();
        button.Margin = new Thickness(4);
        button.Content = DefinitionSet.button;
        panel.Children.Add(label);
        panel.Children.Add(button);
        panel.Children.Add(textBox);
        DockPanel.SetDock(label, Dock.Top);
        DockPanel.SetDock(button, Dock.Bottom);
        this.Content = panel;
        button.Click += (sender, eventArgs) => {
            if (CollaborationWindow != null)
                CollaborationWindow.Add(textBox.Text);
        }; //button.Click
        textBox.TextChanged += (sender, eventArgs) => {
            button.IsEnabled = !string.IsNullOrEmpty(textBox.Text);
        }; //textBox.TextChanged
        button.IsEnabled = false;
    } //MainWindow
    internal ICollaboration CollaborationWindow { get; set; }
} //class MainWindow

And the entry point is implemented as the method of the class derived from Application:

class EntryPoint : Application {
    protected override void OnStartup(StartupEventArgs e) {
        this.ShutdownMode = ShutdownMode.OnMainWindowClose;
        MainWindow mainWindow = new MainWindow();
        this.MainWindow = mainWindow;
        ShowWindow anotherWindow = new ShowWindow();
        mainWindow.CollaborationWindow = anotherWindow;
        anotherWindow.Show();
        mainWindow.Show();
        anotherWindow.Owner = mainWindow;
    } //OnStartup
    [STAThread]
    static void Main() {
        new EntryPoint().Run();
    } //Main
} //class EntryPoint

Again, the XAML-based code is also available; to see it, please download the code sample file.

6 Some Notes on Other Solutions

Even though articles and answers published on CodeProject really provide some solutions, I don't think they deserve separate considerations. I never saw the interface approach, and other approaches are classified in some ad-hoc manner, mixing up method of passing a reference, types of members, passing data, such as "property approach", "object approach", "constructor approach", "delegates approach", "main form", "child to parent", "parent to child", ignoring the fact that the cases overlap (which makes the set of cases to be non-classes), which only conceals the essence of things.

The really key point of form collaboration is the proper abstraction between forms, so passing direct access to some controls is a bad idea in all cases. Actually, event handling and the use of delegate instances between form also provides proper level of abstraction, only this is related to some more "advanced" requirements, when inversion of control is involved. See also: http://en.wikipedia.org/wiki/Inversion_of_control.

I only want to stop at one illusionary aspect of this "problem", parent-child relationship for forms. Not only it is absolutely irrelevant, as I mentioned above, but also plain wrong. Normally, none of the forms is the parent or child of another form. Even though the form is System.Windows.Forms.Control, which has the Parent property, trying to set this property for a form instance throws an exception. It can be worked around by setting System.Windows.Forms.Forms.TopLevel to false, but it makes very little sense. This way, the child-parent relationships between forms is effectively made defunct. Forms participate in important owner-owned relationship which I used in our examples, but it is also totally irrelevant to the subject.

Please see:
https://msdn.microsoft.com/en-us/library/system.windows.forms.control.parent(v=vs.110).aspx,
https://msdn.microsoft.com/en-us/library/system.windows.forms.form.toplevel(v=vs.110).aspx,
https://msdn.microsoft.com/en-us/library/system.windows.forms.form.owner(v=vs.110).aspx,
https://msdn.microsoft.com/en-us/library/system.windows.forms.form.ownedforms(v=vs.110).aspx.

With WPF, the situation is a bit different: there are two hierarchies, logical and visual trees: https://msdn.microsoft.com/en-us/library/ms753391%28v=vs.110%29.aspx.

In WPF terminology, the owner window is sometimes called "parent" (in exception message, for example), but this is also remain totally irrelevant to the subject.

7 Is the Collaboration Good or Evil?

The question may look strange, because such collaboration can be a part of some requirements. But why? I would say that such collaboration is often (but perhaps not always) the sign of bad UI design.

The whole idea to have several forms looks questionable in many cases. (I don't count some modal dialogs here.) In most cases, the design based on only one form or window looks better. The forms/windows are initially designed to be more or less independent. Their parent (in the sense of Windows API, not .NET) is always the desktop, designed to hold relatively independent items which are arranged directly by the users, individually. If the application is represented by one form/window, the role of the "child" forms/windows could be played by other controls, such as panels, tab pages of the TabControl, and a lot more.

Of course, there can be a number of exclusions. One such exclusion is having the application which plays some independent roles only united by the common topic. For example, I prefer using my own mail client which consists of the sender and receiver parts. (And, by the way, I paid special effort to simulate "equal" behavior of both forms, so they both look "main" to the user.)

So, multiple non-modal forms/windows can sometimes be useful, so the ability to organize collaboration between them can be important.

8 Conclusions

Forms/windows collaborations based on interface provides proper encapsulation and isolation of those forms/windows. It serves as "umbrella" technique for any thinkable kind of collaborations, making the boundary between forms/windows transparent, as if the collaboration was done inside the same instance. This collaboration is quite trivial.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sergey Alexandrovich Kryukov
Architect
United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 Pinprofessionalphil.o23-Mar-15 10:30 
GeneralRe: My vote of 5 PinmvpSergey Alexandrovich Kryukov23-Mar-15 10:36 
AnswerGood one.. PinprofessionalAfzaal Ahmad Zeeshan23-Mar-15 7:28 
GeneralRe: Good one.. PinmvpSergey Alexandrovich Kryukov23-Mar-15 9:08 
GeneralMy vote of 5 PinprotectorMaciej Los22-Mar-15 21:54 
GeneralRe: My vote of 5 PinmvpSergey Alexandrovich Kryukov22-Mar-15 22:11 
GeneralRe: My vote of 5 PinprotectorMaciej Los22-Mar-15 22:36 
GeneralThat's why more detail... PinmvpSergey Alexandrovich Kryukov22-Mar-15 22:45 
GeneralRe: That's why more detail... PinprotectorMaciej Los22-Mar-15 22:58 
GeneralRe: That's why more detail... PinmvpSergey Alexandrovich Kryukov22-Mar-15 23:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150327.1 | Last Updated 26 Mar 2015
Article Copyright 2015 by Sergey Alexandrovich Kryukov
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid