Track dynamic form automcatically in winforms - c#

I have an application having 2 forms that opens on application start and 3rd form is getting added on runtime.
I have an another class library that currently monitors the activities of a single form. The below code snippet is of application :-
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Form[] f = new Form[2];
f[0] = new Form1();
f[1] = new Form2();
f[0].Show();
AppContext nvca = new AppContext(new Form1(), new Form2());
Application.Run(nvca);
}
AppContext class is in class library where I am trying to catch all the forms of the application whether it is static or comes at runtime :-
public class AppContext:ApplicationContext
{
public static Form[] AvailForms = null;
public AppContext(params Form[] forms)
{
AvailForms = forms;
UAction ua = new UAction();
foreach (Form f in forms)
{
for (int i = 0; i < f.Controls.Count; i++)
{
ua.setClickHandlerAsync(f.Controls[i]);
}
}
}
public void setClickHandlerAsync(Control item)
{
//Have to recursively get all the element to wrap the click listener.
item.MouseClick += ClickHandlerAsync;
}
private async void ClickHandlerAsync(object sender, MouseEventArgs e)
{
Console.WriteLine("Click Handler called");
}
}
I am searching for a way through which I can track all the forms that are either already added in application or added at runtime.
I have tried through ApplicationContext, but it failed to capture the win events like click, text change, form events even though all the event handlers has been set properly on it.
Any help would be appreciated.

Related

Repopulate a form field from a different class in C#

I'm trying to repopulate a textbox from a separate class. I've looked through a number of instances of this same question and found what I thought was a good solution. But, I can't make it work. I've tried to resolve the issue by creating a separate thread to send the data back. I don't know if this is a great idea or not. But, I know the data is getting back to the correct place without it because it shows up in the console. Any suggestions? Thanks!
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void updater(double value)
{
textBox1.Text = value.ToString(); // Trying to update here
Console.WriteLine(value); // The new multiple makes if back to here...
}
private void button1_Click(object sender, EventArgs e)
{
CALC c = new CALC();
c.valuecalculator(.0025);
}
}
public class CALC
{
public void valuecalculator(double multiplier)
{
for (int index = 0; index < 1000; index++)
{
Form1 f = new Form1();
double newMultiple = index * multiplier;
f.updater(newMultiple);
}
}
}
You're making a new copy of the form in your valuecalculator method, but you should be using the same form.
There are loads of ways to solve this.
You could pass an instance of the form into your valuecalculator method.
You could make the reference to the form static in your Program.cs or whatever startup file originally initialises it.
You could give the form a reference to itself
You could put the code to update the form in the button1 click event (this makes most sense) by making the valuecalculator return the result instead of returning void
Your components should only do one thing. The calculator should perform a calculation and return the result:
public static class Calc
{
public static double CalculateValue(double multiplier)
{
return 100 * multiplier;
}
}
Forms should be as simple as possible. Meaning they are only concerned with displaying form elements and passing events to event handlers. The actual logic that happens in these event handlers should be someone else's responsibility. I like to pass this logic in the constructor:
public partial class Form1 : Form
{
public Form1(Func<double, double>CalculateValue)
{
InitializeComponent();
button1.Click += (sender, eventArgs) => textBox1.Text = CalculateValue(.0025).ToString();
}
}
Constructing and connecting classes with each other is another responsibility. The simplest version is to use your Main() method:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form1(Calc.CalculateValue);
Application.Run(form);
}
}

Switching currently displayed form (winforms)

If I'm going about things COMPLETELY wrong here please tell me the better way to do it. Also sorry for any bad formatting / bad question wording; I haven't really used SO before.
I'm making a maths app which will draw polygons and other shapes and work out the area.
I have a menustrip where you can choose the shape to draw:
I want to make it so when you click one of the buttons (polygon/circle) it will take you to the associated form (I will post the forms below):
I'm trying to control the currently displayed form with a C# class i've made called "MenuManager.cs" however it's giving me an error:
System.TypeInitializationException: 'The type initializer for 'MathsApp.PolygonCalculator' threw an exception.' ArgumentException: Delegate to an instance method cannot have null 'this'
. I will post the code below:
public class MenuManager
{
/* Reference to forms */
PolygonCalculator polygonCalculator = new PolygonCalculator();
CircleCalculator circleCalculator = new CircleCalculator();
/* I believe this is causing the errors. */
public void MenuClickPolygon(object sender, EventArgs e)
{
polygonCalculator.Show();
}
public void MenuClickCircle(object sender, EventArgs e)
{
circleCalculator.Show();
}
}
This is how i'm accessing the MenuManager class and trying to change the displayed form:
this.Menu = new MainMenu();
MenuItem item = new MenuItem("Shapes");
this.Menu.MenuItems.Add(item);
item.MenuItems.Add("Polygon", new EventHandler(menuManager.MenuClickPolygon));
item.MenuItems.Add("Circle", new EventHandler(menuManager.MenuClickCircle));
public partial class PolygonCalculator : Form
{
public static Color penColour = Color.Black;
public static Random rnd = new Random();
public static MenuManager menuManager = new MenuManager();
public PolygonCalculator()
{
InitializeComponent();
InstantiateMenu();
}
public void InstantiateMenu()
{
this.Menu = new MainMenu();
MenuItem item = new MenuItem("Shapes");
this.Menu.MenuItems.Add(item);
item.MenuItems.Add("Polygon", new EventHandler(menuManager.MenuClickPolygon));
item.MenuItems.Add("Circle", new EventHandler(menuManager.MenuClickCircle));
}
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new PolygonCalculator());
}

How to call a function on every form load in C#?

suppose I have 100 windows form written in C#, is there a way to define a function to be called on the load of any of these forms without needing to change the code of any of these forms, and without inheriting from other form, that is a function that is called automatically whenever a new form is opening?
Thanks,
You can use MessageFilter and intercept the message for the loading of forms. Below is a sample for intercepting forms and then adding an event handler to an event. You can do whatever you need in the event handler.
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.AddMessageFilter(new TestMessageFilter());
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
I know you need to run code just before the load event but for some reason subscribing to the Load event does not fire in the code below. But if you can do it in Activated event, then it will work. Or if you want to do it in another event then just modify the code below and see if that event gets triggered. The code is also keeping a list of all the forms so you don't add multiple handlers for the same event of the same form. When the form is closed, it will remove all the handlers.
[SecurityPermission(SecurityAction.LinkDemand, Flags =
SecurityPermissionFlag.UnmanagedCode)]
public class TestMessageFilter : IMessageFilter
{
private Hashtable forms = new Hashtable();
public bool PreFilterMessage(ref Message m)
{
Control c = Control.FromHandle(m.HWnd);
var form = c as Form;
if (form != null &&
!this.forms.ContainsKey(form))
{
form.Load += Form_Load;
form.Activated += Form_Activated;
form.FormClosed += Form_FormClosed;
this.forms.Add(form, form);
}
return false;
}
private void Form_FormClosed(object sender, FormClosedEventArgs e)
{
if (this.forms.ContainsValue(sender))
{
var f = sender as Form;
f.Activated -= Form_Activated;
f.Load -= Form_Load;
this.forms.Remove(sender);
}
}
private void Form_Activated(object sender, EventArgs e)
{
MessageBox.Show("Form_Activated...");
}
private void Form_Load(object sender, EventArgs e)
{
MessageBox.Show("Form_Load...");
}
}
There is no such other way. You just have to use one of the solutions you've said in your question.
You could use a form factory and implement interception to intercept the OnLoad method or any other virtual method within the Form class.
I created a proof of concept below using Autofac, Castle.Core, and Autofac.Extras.DynamicProxy to intercept the OnLoad method of each form and write to the console before and after the OnLoad method was called on the form.
Form factory passing back Owned. CreateForm(string formName) takes the name of a named Autofac service.
public interface IFormFactory
{
Owned<Form> CreateForm(string formName);
}
public class FormFactory : IFormFactory
{
private readonly IContainer _container;
public FormFactory(IContainer container)
{
_container = container;
}
public Owned<Form> CreateForm(string formName)
{
return _container.ResolveNamed<Owned<Form>>(formName);
}
}
The interceptor below will be called every time a virtual method on the form. I added a check for OnLoad however you can intercept and call for other virtual methods as well.
public class FormInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
bool isFormOnLoad = invocation.InvocationTarget is Form && invocation.Method.Name.Equals("OnLoad");
if(isFormOnLoad)
{
Console.WriteLine("Before OnLoad");
}
invocation.Proceed();
if(isFormOnLoad)
{
Console.WriteLine("After OnLoad");
}
}
}
Register your interceptor, forms, and factory taking care to used named services. The names will used to create the forms in the factory method.
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
var builder = new ContainerBuilder();
builder.RegisterType<FormInterceptor>();
Castle.DynamicProxy.Generators.AttributesToAvoidReplicating.Add<System.Security.Permissions.UIPermissionAttribute>();
// Register your forms
builder.RegisterType<frmMain>()
.Named<Form>("frmMain")
.EnableClassInterceptors()
.InterceptedBy(typeof(FormInterceptor));
builder.RegisterType<frmSubForm>()
.Named<Form>("frmSubForm")
.EnableClassInterceptors()
.InterceptedBy(typeof(FormInterceptor));
FormFactory = new FormFactory(builder.Build());
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(FormFactory.CreateForm("frmMain").Value);
}
public static IFormFactory FormFactory { get; set; }
}
Below is an example of creating and showing a test form within the main form:
private void button1_Click(object sender, EventArgs e)
{
using (var subForm = Program.FormFactory.CreateForm("frmSubForm").Value)
{
subForm.ShowDialog(this);
}
}

Access WinForm in OnFocusChanged()

I have created a list "exe" and new WinForm in Main(). I need to access both in OnFocusChanged(). The idea is that a hidden WinForm is created, and when "firefox" has focus, the WinForm will be displayed. My WinForm has a method, update(), that is used to show the WinForm. If I call "form.update()" in Main, the WinForm appears, however I cannot access it in OnFocusChanged().
How can I access the list and WinForm object in OnFocusChanged()? Thanks.
namespace WinForm1
{
static class Program
{
[STAThread]
static void Main()
{
List<string> exe = new List<string>();
Form1 form = new Form1();
Application.Run(form);
}
static private void OnFocusChanged(object sender, AutomationFocusChangedEventArgs e)
{
string program = "firefox";
if(exe.Any(program.Contains)
{
form.update(true);
}
}
}
}
exe can't be accessed from the OnFocusChanged event handler because it's local in scope to the Main method.
You need to make the list variable static and bring it outside of the method
...
static List<string> exe = new List<string>();
[STAThread]
static void Main()
...
you can use System.Diagnostics.Process for archive to this idea
first get list of all process and check process name
Process[] processes = System.Diagnostics.Process.GetProcesses();
if (processes.Any(c => c.ProcessName == "firefox"))
{
//your update code
}

Setting Control properties from separate thread/class

I've searched and can't find a solution that helps me get text from a thread running in a separate class, back to a listbox on the form that created the thread.
Basically I have a class that holds a "test", it is called in it's own thread from a test window. What I want to be able to do is add text to a listbox on the main form to let the user know what is going on with a test. All the examples I can find on Invoke show how to do it within the same class.
Where I start the thread:
PermeabilityTest Run_Test = new PermeabilityTest();
public Thread WorkerThread;
private void button2_Click(object sender, EventArgs e)
{
//enable timer for test duration display
timer1.Enabled = true;
//create and start new thread.
WorkerThread = new Thread(Run_Test.RunTest);
WorkerThread.Start();
}
Here is my class that actually does the work, where I need to get text back to a listbox on a separate form from.
public class PermeabilityTest
{
//volatile alerts the compiler that it will be used across threads.
private volatile bool aborted;
public void RequestStop()
{
//handle saving data file here as well.
aborted = true;
}
public void RunTest()
{
//reference the comms class so we can communicate with the machine
PMI_Software.COMMS COM = new COMMS();
//some test stuffs here
int x = 0;
while( x < 100 && !aborted)
{
System.Diagnostics.Debug.Write("Well here it is, running it's own thread." + Environment.NewLine);
COM.Pause(1);
}
}
}
I would appreciate any one who could help me understand how to get some text back to a listbox on the same form that has the button which starts the thread.
Option 1: (Preffered) Add an event on PermeabilityTest and register on that event in your main form.
Then modify the content of your List box from within your main form.
Example:
Your main form:
PermeabilityTest Run_Test = new PermeabilityTest();
public Thread WorkerThread;
public form1()
{
// Register on the Progress event
Run_Test.Progress += Run_Test_Progress;
}
void Run_Test_Progress(string message)
{
if(listBox.InvokeRequired)
{
// Running on a different thread than the one created the control
Delegate d = new ProgressEventHandler(Run_Test_Progress);
listBox.Invoke(d, message);
}
else
{
// Running on the same thread which created the control
listBox.Items.Add(message);
}
}
private void button2_Click(object sender, EventArgs e)
{
//enable timer for test duration display
timer1.Enabled = true;
//create and start new thread.
WorkerThread = new Thread(Run_Test.RunTest);
WorkerThread.Start();
}
new Delegate:
public delegate void ProgressEventHandler(string message);
Modified PermeabilityTest class:
public class PermeabilityTest
{
//volatile alerts the compiler that it will be used across threads.
private volatile bool aborted;
public event ProgressEventHandler Progress;
public void RequestStop()
{
//handle saving data file here as well.
aborted = true;
}
public void RunTest()
{
//reference the comms class so we can communicate with the machine
PMI_Software.COMMS COM = new COMMS();
//some test stuffs here
int x = 0;
while (x < 100 && !aborted)
{
// Report on progress
if(Progress != null)
{
Progress("This message will appear in ListBox");
}
System.Diagnostics.Debug.Write("Well here it is, running it's own thread." + Environment.NewLine);
COM.Pause(1);
}
}
}
Option 2:
You could make PermeabilityTest an inner class of your main form, and by doing so, allow it to access private members of your main form.
Then you need to pass a reference of your main form to the constructor of PermeabilityTest and keep it as a member.
Option 3:
pass your list box to the constructor of PermeabilityTest
Don't forget to use Invoke on your control since you are running from a different thread.

Categories

Resources