Previous User Controls stay active after changing ContenControl content - c#

In my WPF application I have one main window and two User Controls and I use the following code to change in between my User Controls:
private void SwitchControls(MyButton btnCurrent)
{
switch (btnCurrent.Name)
{
case "btnLine":
{
winLine win = new winLine();
ccContent.Content = win;
}
break;
case "btnHistory":
{
winHistory win = new winHistory();
ccContent.Content = win;
}
break;
}
}
ccContent is my ContentControl inside my main window. winLine and winHistory are my two User Controls. In my winLine I have a timer that Ticks every 5 secs and whenever I switch to winHistory timer in previous User Control keeps ticking. I tried to do ccContent.Content = null; before switching User Controls, but it didn't help.
How do I disable or remove previous User Control and keep only an active one? I hope my question is clear.

Stop and dispose the timer before you reset the Content property. You could do this by trying to cast the value of the Content property to winLine using the as operator:
private void SwitchControls(MyButton btnCurrent)
{
winLine current = ccContent.Content as winLine;
if (current != null && current.Timer != null)
{
current.Timer.Stop();
current.Timer.Dispose();
}
switch (btnCurrent.Name)
{
case "btnLine":
{
winLine win = new winLine();
ccContent.Content = win;
}
break;
case "btnHistory":
{
winHistory win = new winHistory();
ccContent.Content = win;
}
break;
}
}

Related

Capture Button Click event inside a MessageBox in another application

I want to capture the OK Button's Click event on a MessageBox shown by another WinForms application.
I want to achieve this using UI Automation. After some research, I have found that IUIAutomation::AddAutomationEventHandler will do the work for me.
Though, I can capture the Click event of any other button, I'm unable to capture a Click event of the MessageBox.
My code is as follows:
var FindDialogButton = appElement.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, "OK"));
if (FindDialogButton != null)
{
if (FindDialogButton.GetSupportedPatterns().Any(p => p.Equals(InvokePattern.Pattern)))
{
Automation.AddAutomationEventHandler(InvokePattern.InvokedEvent, FindDialogButton, TreeScope.Element, new AutomationEventHandler(DialogHandler));
}
}
private void DialogHandler(object sender, AutomationEventArgs e)
{
MessageBox.Show("Dialog Button clicked at : " + DateTime.Now);
}
EDIT:
My Complete code is as follows:
private void DialogButtonHandle()
{
AutomationElement rootElement = AutomationElement.RootElement;
if (rootElement != null)
{
System.Windows.Automation.Condition condition = new PropertyCondition
(AutomationElement.NameProperty, "Windows Application"); //This part gets the handle of the Windows application that has the MessageBox
AutomationElement appElement = rootElement.FindFirst(TreeScope.Children, condition);
var FindDialogButton = appElement.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, "OK")); // This part gets the handle of the button inside the messagebox
if (FindDialogButton != null)
{
if (FindDialogButton.GetSupportedPatterns().Any(p => p.Equals(InvokePattern.Pattern)))
{
Automation.AddAutomationEventHandler(InvokePattern.InvokedEvent, FindDialogButton, TreeScope.Element, new AutomationEventHandler(DialogHandler)); //Here I am trying to catch the click of "OK" button inside the MessageBox
}
}
}
}
private void DialogHandler(object sender, AutomationEventArgs e)
{
//On Button click I am trying to display a message that the button has been clicked
MessageBox.Show("MessageBox Button Clicked");
}
I tried to keep this procedure as generic as possible, so that it will work whether the application you're watching is already running when your app is started or not.
You just need to provide the watched Application's Process Name or its Main Window Title to let the procedure identify this application.
Use one of these Fields and the corresponding Enumerator:
private string appProcessName = "theAppProcessName"; and
FindWindowMethod.ProcessName
// Or
private string appWindowTitle = "theAppMainWindowTitle"; and
FindWindowMethod.Caption
passing these values to the procedure that starts the watcher, e.g., :
StartAppWatcher(appProcessName, FindWindowMethod.ProcessName);
As you can see - since you tagged your question as winforms - this is a complete Form (named frmWindowWatcher) that contains all the logic required to perform this task.
How does it work:
When you start frmWindowWatcher, the procedure verifies whether the watched application (here, identified using its Process name, but you can change the method, as already described), is already running.
If it is, it initializes a support class, ElementWindow, which will contain some informations about the watched application.
I added this support class in case you need to perform some actions if the watched application is already running (in this case, the ElementWindow windowElement Field won't be null when the StartAppWatcher() method is called). These informations may also be useful in other cases.
When a new Windows is opened in the System, the procedure verifies whether this Window belongs to the watched application. If it does, the Process ID will be the same. If the Windows is a MessageBox (identified using its standard ClassName: #32770) and it belongs to the watched Application, an AutomationEventHandler is attached to the child OK Button.
Here, I'm using a Delegate: AutomationEventHandler DialogButtonHandler for the handler and an instance Field (AutomationElement msgBoxButton) for the Button Element, because these references are needed to remove the Button Click Handler when the MessageBox is closed.
When the MessageBox's OK Button is clicked, the MessageBoxButtonHandler method is called. Here, you can determine which action to take at this point.
When the frmWindowWatcher Form is closed, all Automation Handlers are removed, calling the Automation.RemoveAllEventHandlers() method, to provide a final clean up and prevent your app from leaking resources.
using System.Diagnostics;
using System.Linq;
using System.Windows.Automation;
using System.Windows.Forms;
public partial class frmWindowWatcher : Form
{
AutomationEventHandler DialogButtonHandler = null;
AutomationElement msgBoxButton = null;
ElementWindow windowElement = null;
int currentProcessId = 0;
private string appProcessName = "theAppProcessName";
//private string appWindowTitle = "theAppMainWindowTitle";
public enum FindWindowMethod
{
ProcessName,
Caption
}
public frmWindowWatcher()
{
InitializeComponent();
using (var proc = Process.GetCurrentProcess()) {
currentProcessId = proc.Id;
}
// Identify the application by its Process name...
StartAppWatcher(appProcessName, FindWindowMethod.ProcessName);
// ... or by its main Window Title
//StartAppWatcher(appWindowTitle, FindWindowMethod.Caption);
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
Automation.RemoveAllEventHandlers();
base.OnFormClosed(e);
}
private void StartAppWatcher(string elementName, FindWindowMethod method)
{
windowElement = GetAppElement(elementName, method);
// (...)
// You may want to perform some actions if the watched application is already running when you start your app
Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement,
TreeScope.Subtree, (elm, e) => {
AutomationElement element = elm as AutomationElement;
try
{
if (element == null || element.Current.ProcessId == currentProcessId) return;
if (windowElement == null) windowElement = GetAppElement(elementName, method);
if (windowElement == null || windowElement.ProcessId != element.Current.ProcessId) return;
// If the Window is a MessageBox generated by the watched app, attach the handler
if (element.Current.ClassName == "#32770")
{
msgBoxButton = element.FindFirst(TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "OK"));
if (msgBoxButton != null && msgBoxButton.GetSupportedPatterns().Any(p => p.Equals(InvokePattern.Pattern)))
{
Automation.AddAutomationEventHandler(
InvokePattern.InvokedEvent, msgBoxButton, TreeScope.Element,
DialogButtonHandler = new AutomationEventHandler(MessageBoxButtonHandler));
}
}
}
catch (ElementNotAvailableException) {
// Ignore: this exception may be raised if you show a modal dialog,
// in your own app, that blocks the execution. When the dialog is closed,
// AutomationElement element is no longer available
}
});
Automation.AddAutomationEventHandler(WindowPattern.WindowClosedEvent, AutomationElement.RootElement,
TreeScope.Subtree, (elm, e) => {
AutomationElement element = elm as AutomationElement;
if (element == null || element.Current.ProcessId == currentProcessId || windowElement == null) return;
if (windowElement.ProcessId == element.Current.ProcessId) {
if (windowElement.MainWindowTitle == element.Current.Name) {
windowElement = null;
}
}
});
}
private void MessageBoxButtonHandler(object sender, AutomationEventArgs e)
{
Console.WriteLine("Dialog Button clicked at : " + DateTime.Now.ToString());
// (...)
// Remove the handler after, since the next MessageBox needs a new handler.
Automation.RemoveAutomationEventHandler(e.EventId, msgBoxButton, DialogButtonHandler);
}
private ElementWindow GetAppElement(string elementName, FindWindowMethod method)
{
Process proc = null;
try {
switch (method) {
case FindWindowMethod.ProcessName:
proc = Process.GetProcessesByName(elementName).FirstOrDefault();
break;
case FindWindowMethod.Caption:
proc = Process.GetProcesses().FirstOrDefault(p => p.MainWindowTitle == elementName);
break;
}
return CreateElementWindow(proc);
}
finally {
proc?.Dispose();
}
}
private ElementWindow CreateElementWindow(Process process) =>
process == null ? null : new ElementWindow(process.ProcessName) {
MainWindowTitle = process.MainWindowTitle,
MainWindowHandle = process.MainWindowHandle,
ProcessId = process.Id
};
}
Support class, used to store informations on the watched application:
It's initialized using the App's Process Name:
public ElementWindow(string processName)
but of course you can change it as required, using the Window Title as described before, or even remove the initialization's argument if you prefer (the class just need to not be null when the watched Application has been detected and identified).
using System.Collections.Generic;
public class ElementWindow
{
public ElementWindow(string processName) => this.ProcessName = processName;
public string ProcessName { get; set; }
public string MainWindowTitle { get; set; }
public int ProcessId { get; set; }
public IntPtr MainWindowHandle { get; set; }
}

Tabbed Menu - each tab as a separate CS file

I'm new to C# and have created a basic program.
My "Main menu" is setup with 7 tabs along the top.
I have set up CASE so when the user selects the tab it adds that specific submenu (frmXXXmenu) to the controls.
The issue is
Is this correct
Now each form will open each click, thus I keep generating instances of the forms (and I can see my process memory increasing!)
private void tabmain_SelectedIndexChanged(object sender, EventArgs e)
{
string curtab = tabmain.SelectedTab.Name.ToString();
switch (tabmain.SelectedTab.Name)
{
case "tabcollect":
frmcollectmenu frmcollectmenu = new frmcollectmenu();
frmcollectmenu.TopLevel = false;
tabcollect.Controls.Add(frmcollectmenu);
frmcollectmenu.Dock = DockStyle.Fill;
frmcollectmenu.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
frmcollectmenu.Show();
break;
case "tabpack":
frmpackmenu frmpackmenu = new frmpackmenu();
frmpackmenu.TopLevel = false;
tabpack.Controls.Add(frmpackmenu);
frmpackmenu.Dock = DockStyle.Fill;
frmpackmenu.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
frmpackmenu.Show();
break;
}
}
Etc for 7 tabs.
Am I in the right direction and if so, how do I fix this instance situation?
I was thinking either Dispose when no longer active.. or somehow not creating a new instance when one is already open?
Second issue
One of the forms takes about 2 seconds to open (it is showing a chart based on sql). How can i load this page in the tab when i open frmmain() to start the program and then when i open the tab it will be loaded?
I tried putting
frmcollectmenu frmcollectmenu = new frmcollectmenu();
frmcollectmenu.TopLevel = false;
tabcollect.Controls.Add(frmcollectmenu);
frmcollectmenu.Dock = DockStyle.Fill;
frmcollectmenu.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
frmcollectmenu.visible = false;
frmcollectmenu.Show();
(added the visible part) then i made it visible again when selecting the tab, but it seems it still takes a few seconds and doesnt really work right.
If you want only one instance of your forms opened then you should check the Application.OpenForms collection if one of your forms is already referenced there by the system
case "tabcollect":
frmcollectmenu f = Application.OpenForms["frmcollectmenu"];
if(f == null)
{
frmcollectmenu frmcollectmenu = new frmcollectmenu();
frmcollectmenu.TopLevel = false;
tabcollect.Controls.Add(frmcollectmenu);
frmcollectmenu.Dock = DockStyle.Fill;
frmcollectmenu.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
frmcollectmenu.Show();
}
else
f.Show();
break;
Notice that this works if you leave the name property of the form to its default name (same as the class name of the form)
You can also try to isolate the logic about your open forms in separate methods for each form and implement a dictionary where you store the tab key and the action to execute when the user select that particular tab
// At form global level....
Dictionary<string, Action> tabActions = new Dictionary<string, Action>()
{
{ "tabCollect", runCollect },
{ ..... },
so on for other tabs
}
// The logic for a particular form (the same are needed for others forms)
private static void runCollect()
{
frmcollectmenu f = Application.OpenForms["frmcollectmenu"];
if(f == null)
{
frmcollectmenu frmcollectmenu = new frmcollectmenu();
frmcollectmenu.TopLevel = false;
tabcollect.Controls.Add(frmcollectmenu);
frmcollectmenu.Dock = DockStyle.Fill;
frmcollectmenu.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
frmcollectmenu.Show();
}
else
f.Show();
}
Now you can remove the switch in your SelectedIndexChanged method and just select the appropriate action from the dictionary statically build at startup time of your main form
private void tabmain_SelectedIndexChanged(object sender, EventArgs e)
{
string curtab = tabmain.SelectedTab.Name.ToString();
if(tabActions.ContainsKey(curTab))
tabActions[curTab].Invoke();
}

how to allow only one instance of Internet explorer at any time?

I have used this method i found online, which will take a Uri then check if the uri is opened in an IE browser if it is opened it will prevent the opening of new tab or instance of the same site, if not a new IE tab will open which is great for what i was looking for the method is bellow:
public static void LaunchSite(Uri sitUrl)
{
SHDocVw.ShellWindows w = new SHDocVw.ShellWindows();
bool found = false;
foreach (SHDocVw.ShellBrowserWindow item in w)
{
var doc = item.LocationURL;
if (!string.IsNullOrEmpty(doc))
if (doc == sitUrl.AbsoluteUri)
{
found = true;
break;
}
}
if (!found)
Process.Start(sitUrl.AbsoluteUri);
}
Then used the method in a button_click event handler as follow:
private void btnSubs_Click(object sender, RoutedEventArgs e)
{
Uri oxfArt = new Uri(#"http://www.somesite.com/subscriber/");
StartProcess.LaunchSite(oxfArt);
}
my questions are :
within the method how can i check if there is already an instance of IE open, if it is i need to open the site within that IE window not as a new tab or new IE instance, basically i need to be able to have one instance and one windows of all my sites to be opened in current window?.
within the button event handler i need to use switch statement that uses different URI to save me creating different button event handler for each URI, How would I be able to achieve that
The answer to my 2nd question has 2 option for solution:
Button btnSub = e.Source as Button;
if (btnSub != null)
{
Uri uri = new Uri((string)btnSub.Tag);
StartProcess.LaunchSite(uri);
}
within the butonn click event handler.
then in the button triggered the event we need to set the tag to the desired Uri as follow:
<Button x:Name="theorytst" Cursor="Hand" Tag="http://theorytestpro.co.uk/">
the 2nd solution is to use the switch statement using the button x:Name and the button tag property as before then cast button tag as Uri to pass to the calling method StartProcess.LaunchSite(uri);
switch (btnSub.Name)
{
case "oxfArt":
Uri uri = new Uri((string)btnSub.Tag);
StartProcess.LaunchSite(uri);
break;
case "theorytst":
Uri uri1 = new Uri((string)btnSub.Tag);
StartProcess.LaunchSite(uri1);
break;
default:
break;
}
I need to be able to have one instance and one windows of all my sites to be opened in current window?
Process[] pname = Process.GetProcessesByName("iexplore");
if (pname.Length == 0)
{
Process.Start("IEXPLORE.EXE", "http://www.somesite.com/subscriber/");
}
else
{
try
{
foreach (Process proc in Process.GetProcessesByName("iexplore"))
{
proc.Kill();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Within the button event handler i need to use switch statement that uses different URI to save me creating different button event handler for each URI, How would I be able to achieve that
As I understand correctly you want to call various button handlers. You can do it using switch statement. For example:
string input="http://getyourURI.com"; //you should set your own http address.
//It is just an example
Uri uri = new Uri(input);
string address = uri.OriginalString;
switch(address)
{
case "hey.com":
btnExcel_Click(null, null);
break;
case "another.com":
callAnotherBtn_Click(null, null);
break;
}

Displaying a "User control is loading" message while loading a User Control

I have a Winforms Application with a TabStrip Control. During runtime, UserControls are to be loaded into different tabs dynamically.
I want to present a "User Control xyz is loading" message to the user (setting an existing label to visible and changing its text) before the UserControl is loaded and until the loading is completely finished.
My approaches so far:
Trying to load the User Control in a BackgroundWorker thread. This fails, because I have to access Gui-Controls during the load of the UserControl
Trying to show the message in a BackgroundWorker thread. This obviously fails because the BackgroundWorker thread is not the UI thread ;-)
Show the Message, call DoEvents(), load the UserControl. This leads to different behaviour (flickering, ...) everytime I load a UserControl, and I can not control when and how to set it to invisible again.
To sum it up, I have two questions:
How to ensure the message is visible directly, before loading the User control
How to ensure the message is set to invisible again, just in the moment the UserControl is completely loaded (including all DataBindings, grid formattings, etc.)
what we use is similar to this:
create a new form that has whatever you want to show the user,
implement a static method where you can call this form to be created inside itself, to prevent memory leaks
create a new thread within this form so that form is running in a seperated thread and stays responsive; we use an ajax control that shows a progress bar filling up.
within the method you use to start the thread set its properties to topmost true to ensure it stays on top.
for instance do this in your main form:
loadingForm.ShowLoadingScreen("usercontrollname");
//do something
loadingform.CloseLoadingScreen();
in the loading form class;
public LoadingScreen()
{
InitializeComponent();
}
public static void ShowLoadingScreen(string usercontrollname)
{
// do something with the usercontroll name if desired
if (_LoadingScreenThread == null)
{
_LoadingScreenThread = new Thread(new ThreadStart(DoShowLoadingScreen));
_LoadingScreenThread.IsBackground = true;
_LoadingScreenThread.Start();
}
}
public static void CloseLoadingScreen()
{
if (_ls.InvokeRequired)
{
_ls.Invoke(new MethodInvoker(CloseLoadingScreen));
}
else
{
Application.ExitThread();
_ls.Dispose();
_LoadingScreenThread = null;
}
}
private static void DoShowLoadingScreen()
{
_ls = new LoadingScreen();
_ls.FormBorderStyle = FormBorderStyle.None;
_ls.MinimizeBox = false;
_ls.ControlBox = false;
_ls.MaximizeBox = false;
_ls.TopMost = true;
_ls.StartPosition = FormStartPosition.CenterScreen;
Application.Run(_ls);
}
Try again your second approach:
Trying to show the message in a BackgroundWorker thread. This obviously fails because the BackgroundWorker thread is not the UI thread ;-)
But this time, use the following code in your background thread in order to update your label:
label.Invoke((MethodInvoker) delegate {
label.Text = "User Control xyz is loading";
label.Visible = true;
});
// Load your user control
// ...
label.Invoke((MethodInvoker) delegate {
label.Visible = false;
});
Invoke allows you to update your UI in another thread.
Working from #wterbeek's example, I modified the class for my own purposes:
center it over the loading form
modification of its opacity
sizing it to the parent size
show it as a dialog and block all user interaction
I was required to show a throbber
I received a null error on line:
if (_ls.InvokeRequired)
so I added a _shown condition (if the action completes so fast that the _LoadingScreenThread thread is not even run) to check if the form exists or not.
Also, if the _LoadingScreenThread is not started, Application.Exit will close the main thread.
I thought to post it for it may help someone else. Comments in the code will explain more.
public partial class LoadingScreen : Form {
private static Thread _LoadingScreenThread;
private static LoadingScreen _ls;
//condition required to check if the form has been loaded
private static bool _shown = false;
private static Form _parent;
public LoadingScreen() {
InitializeComponent();
}
//added the parent to the initializer
//CHECKS FOR NULL HAVE NOT BEEN IMPLEMENTED
public static void ShowLoadingScreen(string usercontrollname, Form parent) {
// do something with the usercontroll name if desired
_parent = parent;
if (_LoadingScreenThread == null) {
_LoadingScreenThread = new Thread(new ThreadStart(DoShowLoadingScreen));
_LoadingScreenThread.SetApartmentState(ApartmentState.STA);
_LoadingScreenThread.IsBackground = true;
_LoadingScreenThread.Start();
}
}
public static void CloseLoadingScreen() {
//if the operation is too short, the _ls is not correctly initialized and it throws
//a null error
if (_ls!=null && _ls.InvokeRequired) {
_ls.Invoke(new MethodInvoker(CloseLoadingScreen));
} else {
if (_shown)
{
//if the operation is too short and the thread is not started
//this would close the main thread
_shown = false;
Application.ExitThread();
}
if (_LoadingScreenThread != null)
_LoadingScreenThread.Interrupt();
//this check prevents the appearance of the loader
//or its closing/disposing if shown
//have not found the answer
//if (_ls !=null)
//{
_ls.Close();
_ls.Dispose();
//}
_LoadingScreenThread = null;
}
}
private static void DoShowLoadingScreen() {
_ls = new LoadingScreen();
_ls.FormBorderStyle = FormBorderStyle.None;
_ls.MinimizeBox = false;
_ls.ControlBox = false;
_ls.MaximizeBox = false;
_ls.TopMost = true;
//get the parent size
_ls.Size = _parent.Size;
//get the location of the parent in order to show the form over the
//target form
_ls.Location = _parent.Location;
//in order to use the size and the location specified above
//we need to set the start position to "Manual"
_ls.StartPosition =FormStartPosition.Manual;
//set the opacity
_ls.Opacity = 0.5;
_shown = true;
//Replaced Application.Run with ShowDialog to show as dialog
//Application.Run(_ls);
_ls.ShowDialog();
}
}

Simple UI issue in winforms

I have a simple winforms application, on performing operations it shows a child window everytime. If I open a browser window (fully maximized) or some other window as usual
the application goes back with its childwindow, on clicking the exe which is in the taskbar
only the child window gets visible,but the application window doesn't come into view. I want to know how to show both the windows when I select it from taskbar.
childwindow is also a winform,whose toplevel property is set as true,apart from it nothing
is new(JUST BY CLICKING A BUTTON OR CELL IN GRID I CREATE AN OBJECT FOR THE FORM AND USES IT SHOW PROPERTY TO SHOW)
AlertMsgWindow _alertMsg;
void dataGridViewAlerts_MouseDoubleClick(object sender, MouseEventArgs e)
{
try
{
if (!string.IsNullOrEmpty(this.dataGridViewAlerts.getValue(0, this.dataGridViewAlerts.SelectedRow)))
{
this.dataGridViewAlerts.setCellImage(0, this.dataGridViewAlerts.SelectedRow, "NewsIconRead");
if (_alertMsg == null || _alertMsg.IsDisposed)
{
if (_alertMsg != null)
{
_alertMsg.onDeleteMessageRequest -= new DeleteMessage(_alertMsg_onDeleteMessageRequest);
_alertMsg.Dispose();
}
_alertMsg = new AlertMsgWindow();
_alertMsg.onDeleteMessageRequest += new DeleteMessage(_alertMsg_onDeleteMessageRequest);
}
_alertMsg.FillDetails(alertDetails[IDcollection[this.dataGridViewAlerts.SelectedRow]]);
if (!_alertMsg.Visible)
{
_alertMsg.Location = PointToScreen(new Point(this.Width / 4, -this.Height));
_alertMsg.Show(this);
}
if (onReadMessageReq != null)
onReadMessageReq(IDcollection[this.dataGridViewAlerts.SelectedRow]);
}
}
catch (Exception)
{ }
}
Note: THIS IS HAPPENING ONLY IN WINDOWS2000
I used a component named Dotnetmagic.dll,i dont know whether it causes the problem.can somebody helps me to solve this
I replaced these lines
_alertMsg.Location = PointToScreen(new Point(this.Width / 4, -this.Height));
With
_alertMsg.Left = x;
_alertMsg.Top = y;
and it solved my problem

Categories

Resources