Unsure of the actual implementation of events/delegates across multiple classes - c#

I've read through several pages here on Events and Delegates and understand the idea behind them but am unsure of how to use them across multiple classes. Until now, I've simply relied on the IDE to set everything up for me and I didn't realize it worked inside a single class alone.
public class MyForm : Form
{
public MyForm()
{
...
this.Controls.Add(menuBuilder.GenerateMenuForMyForm());
//load other controls into the form to visualize/manipulate data
}
public void UpdateDataInControls()
{
//reloads info into controls based on data in serializable class.
}
}
public class MenuBuilder
{
public MenuStrip GenerateMenuForMyForm()
{
MenuStrip menu = new MenuStrip();
...
ToolStripMenuItem loadfile = new ToolStripMenuItem();
loadfile.name = "loadfile";
loadfile.text = "Load File";
loadfile.Click += new EventHandler(loadfile_Click);
file.DropDownItems.Add(loadfile);
...
return menu;
}
void loadfile_Click(object sender, EventArgs e)
{
//Open a file dialog and deserialize file
//Need to send an event to MyForm letting it know that it needs to
//update controls in the form to reflect the deserialized data.
}
}
So in this instance, I have events working within a single class, but I'm unsure how to set things up so MyForm can receive an event from MenuBuilder. I have tried something like
loadfile.Click += new EventHandler(myFormObject.loadfile_Click);
and make the loadfile_Click() function in MyForm, but that seems counter-intuitive to the idea of driving functionality through events themselves since it needs the form's object itself to be passed into the constructor. If that's the case, I might as well just call the function directly.

Here is one simple way to achieve what your'e looking for. Its a basic event model where a class declares its events, and an observer class subscribes to it.
using System;
using System.Windows.Forms;
public class MyForm : Form
{
public MyForm()
{
var menuBuilder = new MenuBuilder();
menuBuilder.FileLoaded += (sender, args) => UpdateDataInControls();
Controls.Add(menuBuilder.GenerateMenuForMyForm());
//load other controls into the form to visualize/manipulate data
}
public void UpdateDataInControls()
{
//reloads info into controls based on data in serializable class.
}
}
internal class FileLoadedEventArgs : EventArgs
{
// customize event arguments if need be
// e.g. public string FileName {get;set;}
}
public class MenuBuilder
{
// declare event delegate
internal delegate void FileLoadedEvent(object sender, FileLoadedEventArgs e);
// declare event for observers to subscribe
internal event FileLoadedEvent FileLoaded;
public MenuStrip GenerateMenuForMyForm()
{
MenuStrip menu = new MenuStrip();
/*
ToolStripMenuItem loadfile = new ToolStripMenuItem();
loadfile.name = "loadfile";
loadfile.text = "Load File";
loadfile.Click += new EventHandler(loadfile_Click);
file.DropDownItems.Add(loadfile);
*/
return menu;
}
void loadfile_Click(object sender, EventArgs e)
{
// fire the event
FileLoaded(this, new FileLoadedEventArgs());
}
}

Related

Is there a way to activate a Button that exists within another class?

I am using C# and Xamarin. I have two separate classes. One class is essentially the user interface and another class is acting as a custom built generic entry for users to input data and search for results by clicking a button.
Main UI Class:
Class MainPage
{
public MainPage
{
Content = new StackLayout
{
Children =
{
new InputClass // This is my custom built user entry class
{
}.Invoke(ic => ic.Clicked += WhenButtonPressedMethod) // The problem is here, I can't figure out how to call the button within the input class to fire a clicked event.
}
}
}
}
public async void WhenButtonPressedMethod (object sender, EventArgs e)
{
// Supposed to do stuff when the button is pressed
}
InputClass:
public class InputClass : Grid
{
public delegate void OnClickedHandler(object sender, EventArgs e);
public event OnClickHandler Clicked;
public InputClass
{
Children.Add(
new Button {}
.Invoke(button => button.Clicked += Button_Clicked)
)
}
private void Button_Clicked(object sender, EventArgs e)
{
Clicked?.Invoke(this, e);
}
}
The "InputClass" is a grid that holds a title text label, an entry and a button that a user can press to submit and search data. The button in this class is what I'm trying to actually access to invoke/cause a click event so that the method in the main UI class can be called. But, when I try to invoke a click event on the "InputClass" I can't access the button inside of it, I can only access "InputClass" itself which is just a grid with no useful event properties.
Any solutions or ideas?
If you are running into the same problem as mentioned here, follow the code on this page and read through the comments, it covers enough to be able to piece it together. My mistake was attaching Invokes to the wrong objects.
Don't know why fluent Invoke didn't work correctly.
Add the event handlers this way:
public MainPage
{
var ic = new InputClass();
ic.Clicked += WhenButtonPressedMethod;
Content = new StackLayout
{
Children = { ic }
}
}
public InputClass
{
var button = new Button;
button.Clicked += Button_Clicked;
Children.Add(button);
}

Passing data between usercontrols c#

It's known that there are some solutions similar to this one, but I can't solve my problem with them.
I have two user controls:
The first one makes a Report object.
The second one shows it.
I have a main Form that links both controls.
These two controls are created in a DLL, and are added to the
main form like this:
//ADDS THE FIRST CONTROL TO THE PANEL CONTROL
myDll.controlUserReport userControlA = new myDll.controlUserReport();
panelControl1.Controls.Add(userControlA);
userControlA.Dock = DockStyle.Left;
//ADDS THE SECOND CONTROL TO THE PANEL CONTROL
myDll.controlDocViewer userControlB = new myDll.controlDocViewer();
panelControl1.Controls.Add(userControlB);
userControlB.Dock = DockStyle.Fill;
How can I pass the Report object, which is created in the first control controlUserReport when I click over a button, to the other user control controlDocViewer to show it?
You should use events for this. In UserControlA declare the event:
//Declare EventHandler outside of class
public delegate void MyEventHandler(object source, Report r);
public class UserControlA
{
public event MyEventHandler OnShowReport;
private void btnShowReport_Click(object sender, Report r)
{
OnShowReport?.Invoke(this, this.Report);
}
}
In UserControlB subscribe to the event and show the report:
public class UserControlB
{
// Do it in Form_Load or so ...
private void init()
{
userControlA.OnShowReport += userControlA_OnShowReport;
}
private void userControlA_OnShowReport(object sender, Report r)
{
// Show the report
this.ShowReport(r);
}
}
the post above is good except init() method should not be in ControlB, but in the parent form, something like this:
public class frmMain : Form
{
private void frmMain_Load(object sender, EventArgs e)
{
// subscribe/glue
userControlA.OnShowReport += userControlB.OnShowReport;
userControlB.OnShowReport += userControlA.OnShowReport;
}
public class UserControlA
{
public event EventHandlerNodeCopy OnDataCopy;
public TreeNode NodeCopied { get; set; }
private void some_method(string z, TreeNode trn)
{
OnDataCopy?.Invoke(this, trn);
...
}
public void frmJsTree_OnDataCopy(object source, TreeNode tn)
{
NodeCopied = tn;
}
public class UserControlB
{
public event EventHandlerNodeCopy OnDataCopy;
public TreeNode NodeCopied { get; set; }
private void another_method(int i, TreeNode trn)
{
OnDataCopy?.Invoke(this, trn);
...
}
public void frmJsTree_OnDataCopy(object source, TreeNode tn)
{
NodeCopied = tn;
}
enter code here
Another approach is using BehaviorSubject (requires System.Reactive). Once the data is added in the BehaviorSubject, all places that subscribe can see the info. A very basic example:
Create a class to represent your data. Ex:
DataService.cs
public static BehaviorSubject<YourDataType> MyAwesomeData { get; } = new BehaviorSubject<YourDataType> (null);
In You UserControlA (maybe in the clickEvent from the button) or whatever:
private void btnShowReport_Click(object sender, MouseButtonEventArgs e)
{
// Do some stuffs to prepare the data...
YourDataType myDataReportPrepared = null; // something;
// Here you update your DataInfo on BehaviorSubject
DataService.MyAwesomeData.OnNext(myDataReportPrepared);
}
Finally every place in your solution that uses subscribe in that Subject will listen to that data, like for example in your UserControlB:
// This will trigger every time MyAwesomeData.OnNext() is executed.
DataService.MyAwesomeData.Subscribe(item =>
{
if (item != null){
// Do something with it...Like populate some Datagrid...
}
});

Sharing data between child and parent windows (C# WPF)

Scenario:
Three forms: MainWindow, Positions, Calibration self-named (MainWindow : Window etc.)
MainWindow creates an instance of three objects:
Vars: Model Vars = new Model();
Positions: Positions PositionsWindow = new Positions();
Calibration: Calibration CalibrationWindow = new Calibration();
A button in MainWindow is used to Show and Hide the child windows. Form fields in MainWindow update fields in class Vars.
MainWindow code:
public partial class MainWindow : Window
{
Model Vars = new Model();
Positions PositionsWindow = new Positions();
Calibration CalibrationWindow = new Calibration();
private void OpenButton_Click(object sender, RoutedEventArgs e)
{
PositionsWindow.Show();
}
private void TextBoxUpdate_Click(object sender, RoutedEventArgs e)
{
Vars.TestVar = TestBox.Text;
}
}
Question: How can form fields in the child windows update the parent form fields and/or fields in the class "Vars", i.e. passing data from child to parent and trigger an action in the parent form?
Attempts: A similar question suggested passing the main window this, example: Positions PositionsWindow = new Positions(); however, this only works when the object is created in a method. At this point, PositionsWindow.Show(); is no longer valid. i.e. it is only suitable for a child window created and closed in a single method.
I would not really recommend initializing the variables before the constructor. Don't get used to that.
I would change the constructor of each of the three Windows:
public partial class Model : Window{
MainWindow MW;
Model(MainWindow MW){
this.MW = MW;
// other constructor stuff
}
}
Do the same thing for Positions and Calibration.
Obviously, you cannot use this when you are INITIALIZING the Windows BEFORE the constructor is called, because there is still no this to pass.
Therefore, in your MainWindow:
public partial class MainWindow : Window
{
Model Vars; // = new Model(this); <- the constructor was not yet called, there is no this
Positions PositionsWindow; // = new Positions();
Calibration CalibrationWindow; // = new Calibration();
MainWindow(){
Vars = new Model(this);
Positions = new Positions(this);
CalibrationWindow = new Calibration(this);
}
private void OpenButton_Click(object sender, RoutedEventArgs e)
{
PositionsWindow.Show();
}
private void TextBoxUpdate_Click(object sender, RoutedEventArgs e)
{
Vars.TestVar = TestBox.Text;
}
}
Edit: (to complete the answer to the question):
Now, if you want the Windows to change stuff to each other, just create functions in MainWindow that change stuff in each of the Windows. And with MW you can call these functions from any child Window
For me the best is using Subscribe/Publisher event-based way, here is the way to do it. (i recreate the code so that you can understand)
1) add an event publisher in your child windows.
public partial class ChildWindows : Window
{
// the is the event publisher
public event Action<string> ChildUpdated;
public ChildWindows()
{
InitializeComponent();
}
private void updateParentBtn_Click(object sender, RoutedEventArgs e)
{
// pass the parameter.
ChildUpdated(updateTb.Text);
}
}
2) Subscribe the publisher in your parent windows.
public partial class MainWindow : Window
{
Model Vars;
ChildWindows childWindows;
public MainWindow()
{
InitializeComponent();
Vars = new Model();
childWindows = new ChildWindows();
//this is the event subscriber.
childWindows.ChildUpdated += ChildWindows_ChildUpdated;
}
//do whatever you want here.
void ChildWindows_ChildUpdated(string obj)
{
// Update your Vars and parent
Vars.TestVar = obj;
updateLbl.Content = Vars.TestVar;
}
private void openButton_Click(object sender, RoutedEventArgs e)
{
childWindows.Show();
}
private void textBoxUpdate_Click(object sender, RoutedEventArgs e)
{
}
}
3) In this case, when i type inside the textbox in my child windows, and press a button, it will appear on a label inside my parent windows.
P/S : i had changed the ParentUpdated to ChildUpdated. thanks to #Adrian for constructive feedback
example

I need to access a form control from another class (C#)

On my form, I have one Panel container, named "panelShowList".
On my project, i added a new class, which look like this:
class myNewClass
{
private int newPanelPos = 30;
private const int spaceBetweenElements = 30;
private const int panelWidth = 90;
private const int panelHeight = 40;
private int elementPos = 0;
private ArrayList myPanels = new ArrayList() { };
// some irelevant methods
public void addElementPanels(Panel dataPanel, Panel nextPanel)
{
myPanels.Add(dataPanel);
myPanels.Add(nextPanel);
}
public void displayPanels()
{
foreach (Panel tmp in myPanels)
{
// here i'm stuck
// i need to do something like this :
// myMainForm.panelShowList.Controls.Add(tmp);
// of course this is wrong! but i need a method to acces that control
}
}
}
Basically, I need a way to add all Panels from my ArrayList on "panelShowList" control from my form.
I tried something like this:
public void displayPanels()
{
frmMain f = new frmMain();
foreach (Panel tmp in myPanels)
{
f.display(tmp);
// where display(Panel tmp) is a function in my Form, who access
// "panelShowList" control and add a new Panel
}
}
But it only works if i do this:
f.ShowDialog();
and another form is open.
Any suggestions will be appreciated.
Maybe a bit late, but by all means, here is another approach, that's still more clean than David's approach:
You should add an EventHandler in your MyNewClass. Then you can subscribe to that event from within your form.
public partial class Form1 : Form
{
private readonly MyNewClass _myNewClass;
public Form1()
{
InitializeComponent();
_myNewClass = new MyNewClass();
_myNewClass.DisplayPanelsInvoked += DisplayPanelsInvoked;
}
private void DisplayPanelsInvoked(object sender, DisplayPanelsEventArgs e)
{
var panels = e.Panels; // Add the panels somewhere on the UI ;)
}
}
internal class MyNewClass
{
private IList<Panel> _panels = new List<Panel>();
public void AddPanel(Panel panel)
{
_panels.Add(panel);
}
public void DisplayPanels()
{
OnDisplayPanels(new DisplayPanelsEventArgs(_panels));
}
protected virtual void OnDisplayPanels(DisplayPanelsEventArgs e)
{
EventHandler<DisplayPanelsEventArgs> handler = DisplayPanelsInvoked;
if (handler != null)
{
handler(this, e);
}
}
public event EventHandler<DisplayPanelsEventArgs> DisplayPanelsInvoked;
}
internal class DisplayPanelsEventArgs : EventArgs
{
public DisplayPanelsEventArgs(IList<Panel> panels)
{
Panels = panels;
}
public IList<Panel> Panels { get; private set; }
}
In my opinion it's a better solution, because you don't need to provide a reference of the form to the MyNewClass instance. So this approach reduces coupling, because only the form has a dependency to the MyNewClass.
If you always want to "update" the form whenever a panel is added, you could remove the DisplayPanels-method and shorten the code to this:
public partial class Form1 : Form
{
private readonly MyNewClass _myNewClass;
public Form1()
{
InitializeComponent();
_myNewClass = new MyNewClass();
_myNewClass.PanelAdded += PanelAdded;
}
private void PanelAdded(object sender, DisplayPanelsEventArgs e)
{
var panels = e.AllPanels; // Add the panels somewhere on the UI ;)
}
}
internal class MyNewClass
{
private IList<Panel> _panels = new List<Panel>();
public void AddPanel(Panel panel)
{
_panels.Add(panel);
OnPanelAdded(new DisplayPanelsEventArgs(_panels, panel)); // raise event, everytime a panel is added
}
protected virtual void OnPanelAdded(DisplayPanelsEventArgs e)
{
EventHandler<DisplayPanelsEventArgs> handler = PanelAdded;
if (handler != null)
{
handler(this, e);
}
}
public event EventHandler<DisplayPanelsEventArgs> PanelAdded;
}
internal class DisplayPanelsEventArgs : EventArgs
{
public DisplayPanelsEventArgs(IList<Panel> allPanels, Panel panelAddedLast)
{
AllPanels = allPanels;
PanelAddedLast = panelAddedLast;
}
public IList<Panel> AllPanels { get; private set; }
public Panel PanelAddedLast { get; private set; }
}
and another form is open
That's because you're creating an entirely new form:
frmMain f = new frmMain();
If you want to modify the state of an existing form, that code will need a reference to that form. There are a number of ways to do this. One could be to simply pass a reference to that method:
public void displayPanels(frmMain myMainForm)
{
foreach (Panel tmp in myPanels)
{
// myMainForm.panelShowList.Controls.Add(tmp);
// etc.
}
}
Then when your main form invokes that method, it supplies a reference to itself:
instanceOfNewClass.displayPanels(this);
Though, to be honest, it's not really clear what sort of structure you're going for here. If code is modifying a form then I imagine that code should be on that form. It can certainly be organized into a class, but perhaps that can be an inner class of that form since nothing else needs to know about it.
I'm also concerned that your implementation of myNewClass requires methods to be invoked in a specific order. Any given operation on an object should fully encapsulate the logic to complete that operation. Some of that initialization logic may belong in the constructor if the object isn't in a valid state until that logic is completed.
This is all a bit conjecture though, since the object structure isn't clear here.

Adding events to a class using +=

Please forgive my little knowledge!
I have the following class in HIDNewDeviceEventMonitor.cs:
public class HIDNewDeviceEventMonitor : IDisposable
{
// used for monitoring plugging and unplugging of USB devices.
private ManagementEventWatcher watcherAttach;
public HIDNewDeviceEventMonitor()
{
// Catch USB HID plugged instance event watching
watcherAttach = new ManagementEventWatcher();
watcherAttach.EventArrived += new EventArrivedEventHandler(watcher_EventArrived);
watcherAttach.Query = new WqlEventQuery(#"SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_PNPEntity' AND TargetInstance.DeviceID LIKE 'HID\\VID_04D8%'");
watcherAttach.Start();
}
void watcher_EventArrived(object sender, EventArrivedEventArgs e)
{
Debug.WriteLine("my device is inserted..");
}
public void Dispose()
{
watcherAttach.Stop();
watcherAttach.Dispose();
}
~HIDNewDeviceEventMonitor()
{
this.Dispose();
}
}
Now, how can I change this class to be able to add an event handler that the class can call from within watcher_EventArrived where someNewEvent is outside the class file, actually in the form.cs:
// code in the form
HIDNewDeviceEventMonitor ok = new HIDNewDeviceEventMonitor();
ok.Inserted += someNewEvent; // <-- my problem, I don't know how to add an event to the class this way
private void someNewEvent()
{
//Enumerate and add to listbox1
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
ok.Dispose();
}
I 've seen this thing with other classes, how can I make my class like that?
Your Inserted event should look like this:
public event EventHandler Inserted;
You invoke it like this:
private void OnInserted()
{
if (this.Inserted != null)
{
this.Inserted(this, EventArgs.Empty);
}
}
The signature for the event handler is this:
void someNewEvent(object sender, EventArgs e)
{
//
}
Then you should wrap that code in the constructor of the class:
HIDNewDeviceEventMonitor ok;
public ClassName()
{
ok = new HIDNewDeviceEventMonitor();
ok.Inserted += someNewEvent; // <-- my problem
}
Declare the ok variable outside the constructor, and instantiate it inside. Then add the event handler.
Pro tip: You could use the generic EventHandler<T> if you need to supply a custom implementation of e.
Simply put, you're trying to add events to your HIDNewDeviceMonitor class.
To do this, first you'll need to define a delegate.
public delegate void InsertedHandler;
Next, you'll need to define the event in your HIDNewDeviceMonitor class.
// Notice how the event uses the delegate that's been defined
// v
public event InsertedHandler Inserted;
Now you'll need something that "fires" the event, which could easily be put in your watcher_EventArrived method.
void watcher_EventArrived(object sender, EventArrivedEventArgs e)
{
Debug.WriteLine("my device is inserted..");
// Notice how we check the event handler for null.
// If you don't, it could throw a NullReferenceException.
// Irritating, but easy to debug.. Usually..
if (Inserted != null)
Inserted(); // Or whatever parameters you need.
}
We're all done with the HIDNewDeviceMonitor class.
Now whatever class that uses the HIDNewDeviceMonitor can use the EventHandler code that you provided.
However, it'll have to be the same delegate.
public class MyClass
{
HIDNewDeviceMonitor monitor;
public MyClass()
{
monitor = new HIDNewDeviceMonitor();
monitor.Inserted += DeviceInserted;
}
private void DeviceInserted()
{
// Execute code here
}
}
You need to do following in the HIDNewDeviceEventMonitor class:
1.) First define a public event inside the class like this-
public event EventHandler Inserted;
2.) Then fire this event within the code where you detect the changes in events. Like this-
if(Inserted != null)
Inserted(this,null);
The if condition checks if the event is registered by any listener. It's fired in case it is.
Hope this helps.

Categories