Click here to Skip to main content
15,885,278 members
Home / Discussions / C#
   

C#

 
GeneralRe: Share an event between two forms. Pin
jenya722-May-14 4:12
jenya722-May-14 4:12 
GeneralRe: Share an event between two forms. Pin
jenya722-May-14 4:15
jenya722-May-14 4:15 
GeneralRe: Share an event between two forms. Pin
jenya722-May-14 4:53
jenya722-May-14 4:53 
AnswerRe: Share an event between two forms. Pin
joost.versteegen22-May-14 4:40
joost.versteegen22-May-14 4:40 
GeneralRe: Share an event between two forms. Pin
jenya722-May-14 4:56
jenya722-May-14 4:56 
AnswerRe: Share an event between two forms. Pin
BobJanova22-May-14 5:02
BobJanova22-May-14 5:02 
GeneralRe: Share an event between two forms. Pin
jenya722-May-14 5:07
jenya722-May-14 5:07 
GeneralRe: Share an event between two forms. Pin
BobJanova22-May-14 5:47
BobJanova22-May-14 5:47 
Well let's have a look at that from the perspective of the second question you asked (typing on the second form updates values on the first).

First, what's the data model? It's a list of items (displayed in the list box) which have a name (editable on the second):

public class Item : BaseNotifier {
 private string name;
 public string Name {
  get { return name; }
  set { name = value; Notify("Name"); }
 }

 public override string ToString() { return name; }
}

public class DataModel {
 public IList<Item> Items { get; private set; }

 public DataModel() {
  Items = new List<Item>();
 }
}


We're just going for model-view separation here as there's no need for an intermediate view model layer, since the interaction between model and view is so simple. If you're wanting a rigorous MVVM type approach you can include a view model interface (IItem : INotifyPropertyChanged with a single property Name), but we don't need that. BaseNotifier is a simple implementation of INotifyPropertyChanged that you should put in a utility library somewhere, I've written it 10 times at least:

public class BaseNotifier : INotifyPropertyChanged {
 public event PropertyChangedEventHandler PropertyChanged;

 protected void Notify(string property) {
  var handler = PropertyChanged;
  if(null != handler) handler(this, new PropertyChangedEventArgs(property));
 }
}


Now we have a data model, which you can populate on application startup:

DataModel dataModel = new DataModel();
dataModel.Items.Add(new Item { Name = "Item 1" });
dataModel.Items.Add(new Item { Name = "Item 2" });


Now, data binding. I'm going to skip over the details of data binding a list box because it isn't very good, in WinForms, and I don't think it's helpful to go into that in a short post here. Instead, let's just ignore data binding and populate the list control from our data model explicitly:

class Form1 {
 // ...

 private void PopulateListBox(DataModel dataModel) {
  listBox1.Items.Clear();
  foreach(Item item in dataModel.Items) {
   listBox1.Items.Add(item);
   item1.PropertyChanged += HandleItemPropertyChanged;
  }
 }
}


This introduces a small complication for adding/removing items; feel free to investigate BindingList and all that area if you want some wider reading. However, for this example and any where the list is fixed, it will work. There's some dull code in that event handler to update the list, due to the bad implementation of ListBox:

private void HandleItemPropertyChanged(object sender, PropertyChangedEventArgs e) {
 Item item = (Item)sender;
 int index = listBox1.Items.IndexOf(item);
 if(index >= 0) {
  if(e.property == null || e.property == "Name") {
   int previousSelectedIndex = listBox1.SelectedIndex;
   listBox1.Items.Remove(item);
   listBox1.Items.Insert(index, item);
   listBox1.SelectedIndex = previousSelectedIndex;
  }
 }
}


Call that method within the form's constructor, either constructing the data model in place or passing it in as a constructor parameter.

Now, onto the binding on the second form. Give it a property of the item it's editing:

class Form2 : Form {
 // ...

 private Item editingItem;
 public Item EditingItem {
  get { return editingItem; }
  set {
   textBox2.DataBindings.Clear();
   editingItem = value;
   if(null != value) {
    textBox2.DataBindings.Add("Text", value, "Name");
   }
  }
 }
}


(If your list box and text box have sensible names, switch out listBox1 and textBox2 for those names, obviously.)

At this point, the two forms are individually able to bind to the model (the whole model in the case of Form1, one Item in the case of Form2), but we don't have any linkage between them. This is where the concept of the view-model or controller comes in: which item is selected on Form1 is nothing to do with the model, but affects what the view is seeing from the model, so it sits between the model (DataModel and Item) and the view (Form1 and Form2). There are a number of ways to do this; my preferred one is to layer the code such that the view refers to the intermediate layer, and the intermediate layer knows nothing about the view, making it a VM not a controller if my understanding of those patterns is correct.

In this case the VM is very simple:

public class ApplicationVM : BaseNotifier {
 private readonly DataModel model;
 
 public ApplicationVM(DataModel model) { this.model = model; }

 private Item selectedItem;
 public Item SelectedItem {
  get { return selectedItem; }
  set { selectedItem = value; Notify("SelectedItem"); }
 }

 public IList<Items> Items { get { return model.Items; } }
}


Now you need a reference to the VM in the forms, not just the model. (It's possible to structure this so the view gets the item list directly from the DataModel, but it is better not to as the ApplicationVM class is supposed to represent the model from the point of view of what the view needs.) Update Form1's constructor and PopulateListBox appropriately.

Once again I'm going to skip over binding this directly to a list box's SelectedValue or DataSource and suggest that you just attach directly to the SelectedValueChanged event to update it:

Form1 {
 // Attach this to ListBox.SelectedValueChanged
 private void listBoxSelectedValueChanged(object sender, EventArgs e) {
  viewModel.SelectedItem = (Item)listBox.SelectedItem;
 }
}


For this particular case we don't need to bind the other way but if the VM can be updated outside Form1 (for example, Next/Prev buttons on Form2) you might, so here's how to do that:

public Form1(ApplicationVM viewModel) {
 InitialiseComponent(); // this will already be there

 PopulateListBox(viewModel); // you will have added this above
 viewModel.PropertyChanged += (s, e) => listBox1.SelectedItem = viewModel.SelectedItem;
}


Finally, in Form2, make it update the item it's displaying from the VM:

public Form2(ApplicationVM viewModel) {
 InitialiseComponent(); // this will already be there

 viewModel.PropertyChanged += (s, e) => EditingItem = viewModel.SelectedItem;
}


Done Cool | :cool:

At the end we have 2 view classes (Form1 and Form2), two model classes (DataModel and Item) and one VM class (ApplicationVM). The views bind (either with 'real' data binding or event handlers) to properties on the VM and on model classes, without needing to know about what might need to change when they update those properties. You can now switch out views, add new views on different aspects of the model, leave several Form2s up looking at different Items, and serialise or save your model much more easily.

Now I appreciate that this is quite a long post for such a trivial problem. If all this application ever wants to do is pop up a secondary form when you select a different item in a list, it is probably overkill to do it 'properly'. If you write a real application where the view update relationships are complex, this layering will be very helpful. And hopefully this explanation will teach you some things and kick off some interest in further reading, even if you don't need it now.

Here's an article by me expanding on data binding and MVVM-type design in WinForms.
GeneralRe: Share an event between two forms. Pin
jenya722-May-14 7:28
jenya722-May-14 7:28 
Questionc# Pin
Member 1083669622-May-14 1:18
Member 1083669622-May-14 1:18 
AnswerRe: c# Pin
Rob Philpott22-May-14 1:25
Rob Philpott22-May-14 1:25 
GeneralRe: c# Pin
Richard MacCutchan22-May-14 2:01
mveRichard MacCutchan22-May-14 2:01 
AnswerRe: c# Pin
Richard MacCutchan22-May-14 2:02
mveRichard MacCutchan22-May-14 2:02 
AnswerRe: c# Pin
Chris Quinn22-May-14 2:16
Chris Quinn22-May-14 2:16 
AnswerRe: c# Pin
ZurdoDev22-May-14 4:59
professionalZurdoDev22-May-14 4:59 
GeneralRe: c# Pin
Chris Quinn22-May-14 5:02
Chris Quinn22-May-14 5:02 
GeneralRe: c# Pin
ZurdoDev22-May-14 5:06
professionalZurdoDev22-May-14 5:06 
AnswerRe: c# Pin
Dave Kreskowiak22-May-14 4:19
mveDave Kreskowiak22-May-14 4:19 
GeneralRe: c# Pin
Shady George22-May-14 8:11
Shady George22-May-14 8:11 
AnswerRe: c# Pin
ZurdoDev22-May-14 5:00
professionalZurdoDev22-May-14 5:00 
AnswerRe: c# Pin
V.22-May-14 6:21
professionalV.22-May-14 6:21 
Questionusing generic on method Pin
Gilbert Consellado22-May-14 0:40
professionalGilbert Consellado22-May-14 0:40 
AnswerRe: using generic on method Pin
Pete O'Hanlon22-May-14 0:50
mvePete O'Hanlon22-May-14 0:50 
GeneralRe: using generic on method Pin
Gilbert Consellado22-May-14 0:58
professionalGilbert Consellado22-May-14 0:58 
GeneralRe: using generic on method Pin
Pete O'Hanlon22-May-14 1:01
mvePete O'Hanlon22-May-14 1:01 

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

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