WinForms Dialog Form -- Close or Dispose? - winforms

I've inherited some code and wanted to run this modification by you all, my concern is memory management.
Let us say I have a "base" Form with a bunch of buttons that open "dialog" forms. What is the recommended pattern for opening the dialog forms? Currently we display the "dialog" form like so (in the "base" Form code, upon button click):
ChangePasswordForm frm = new ChangePasswordForm();
frm.ShowDialog();
Then close it like so (in the "dialog" form code):
private void bCancel_Click(object sender, EventArgs e)
{
this.Close();
//this.Dispose(); <-- this is what I am considering adding.
}
My rationale for adding Dispose is that I am worried if this form is displayed and closed many times that each time a new instance of the form is created and its resources are never really released -- is this correct? Also, if the form has the "close" X in the top right, should I put a Dispose() call in the FormClosed event as well?
Thanks in advance.

I would use a using statement:
using (var frm = new ChangePasswordForm()) {
frm.ShowDialog();
}
Combine this with a DialogResult:
private void bCancel_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
}
Setting the DialogResult, will close the Dialog, and the caller/owner has some feedback.
And you don't have to worry about Close or Dispose.

According to MSDN you need to dispose under two conditions:
The two conditions when a form is not disposed on Close is when (1) it is part of a multiple-document interface (MDI) application, and the form is not visible; and (2) you have displayed the form using ShowDialog. In these cases, you will need to call Dispose manually to mark all of the form's controls for garbage collection.
MSDN Form.Close
Declaring the form in a using statement would be the appropriate way to handle this.
using (ChangePasswordForm frm = new ChangePasswordForm())
{
frm.ShowDialog();
}

Related

WinForm text box: MenuStrip keyboard shortcuts overriding OS-level copy/paste

Update: I've figured out the source of the issue, now trying to figure out the best fix
I've got a Form with a customized MenuStrip, with all sorts of bells and whistles. Of note here, is that many of my MenuStrip items have keyboard shortcuts - namely ones for Cut/Copy/Paste.
It appears that the presence of this MenuStrip is overriding (and therefore cancelling) the default Cut/Copy/Paste keyboard shortcut behaviors for my text boxes (and other controls).
All of them.
.
I can't really say I have a reason for the MenuStrip Cut/Copy/Paste options, besides the fact that I would expect to see them there. That's how Office type programs operate, and it's something the user (myself included) would expect.
I could remove the Cut/Copy/Paste options from the MenuStrip, but that would be admitting defeat! So how do I keep my overly engineered MenuStrip from forcing me to implement custom code for EVERY control that's Cut/Copy/Paste friendly?
.
** Original Post: **I've got a TextBox control in a toolbar which is to be used throughout my program. Imagine my surprise when native OS-level Copy/Paste events were not supported by default.
Sure, I could code something manually, but when I right-click on the control, Cut/Copy/Paste are already built in. How can I leverage this existing functionality?
I figure adding a KeyDown event with Ctrl+C, P, and X would be about the maximum I should have to code. For those events I just call a built-in method or something. That, or find a setting that enables native cut/copy/paste.
What am I overlooking/missing?
Testing it out native copy and paste does work on a TextBox control unless you are overridding the ContextMenu or ContextMenuStrip, in that case you will need to use the ClipBoard Class to implement it yourself.
In looking at it further this MSDN Forum article discusses sending the Commands to the Native Textbox Control using the SendMessage Method. This is implemented in a Custom TextBox which sounds like what you are doing.
Small excerpt see article for further implementation:
protected void itemCut_Click(object sender, EventArgs e)
{
SendMessage(this.Handle, TextBoxMessages.WM_CUT, 0, 0);
}
public static class TextBoxMessages
{
public const int EM_UNDO = 0x00C7;
public const int WM_CUT = 0x0300;
public const int WM_COPY = 0x0301;
public const int WM_PASTE = 0x0302;
}
Easy Solution: Use the SendKeys.Send() call within the Click event.
SendKeys.Send("^X");
I'm doing something a little more complicated, so here's the details:
I'm making several Forms, which all share some custom controls: MenuStrip, StatusStrip, and a few other custom controls. I've decided to have the Forms all inherit from the same base class, to allow common implementation of lots of stuff.
public partial class CommonFormBase : Form
{
private void Initialize()
{
//Bind click event for custom MenuStrip to events in the local Form
CommonMenuStrip.Edit_Cut.Click += new EventHandler(Edit_Cut_Click);
CommonMenuStrip.Edit_Copy.Click += new EventHandler(Edit_Copy_Click);
CommonMenuStrip.Edit_Paste.Click += new EventHandler(Edit_Paste_Click);
}
//Implement Click events for the MenuStrip by calling local methods
internal void Edit_Cut_Click(object sender, EventArgs e) { Cut(); }
internal void Edit_Copy_Click(object sender, EventArgs e) { Copy(); }
internal void Edit_Paste_Click(object sender, EventArgs e) { Paste(); }
//Generic implementation of common commands for the CommonFormBase
public virtual void Cut() { SendKeys.Send("^X"); }
public virtual void Copy() { SendKeys.Send("^C"); }
public virtual void Paste() { SendKeys.Send("^V"); }
}
I implemented the MenuStrip's click event at the Form level (not the MenuStrip level), but in that event I only call a generic method, which does all the code. In this example it's overkill, but I have other MenuStrip commands that will change in functionality for different child Forms, so I figured having them all work the same would be easier.
Anyway, this works almost perfectly! It seems to push the shortcut-key-activated MenuStrip_Click event to the underlying control (or maybe the Form?), which then implements default shortcut key events.
The only thing it does wrong is it only triggers ONCE when you do Ctrl + V + V + V... or hold Ctrl+V. Still, that's just a matter of the trigger not recognizing multiple events, not an issue with the solution itself.

How to re-open the closed window?

i've seen so many samples that in order to open closed window i should hide the window in closing event, but this is not fair for me if i close window in middle of my work and again open same window it showing me content where i left because i'm hiding window previously. So how can i start freshly my window after closed or hide the window.
currently i'm calling winload method which is to show fresh window after calling the show method of a hide window.
private PurgeData data=new PurgeData();
private void MenuPurgeData_Click(object sender, RoutedEventArgs e)
{
try
{
if (PurgeData == null)
{
PurgeData = new PurgeData();
PurgeData.Show();
}
else
{
PurgeData.WinLoad();
PurgeData.Show();
}
}
Thanks,
#nagaraju.
If you want the behaviour of hide/show, you should not call Window.Close() on the window at all, but hide it by calling Window.Hide(). If the user has closed it though and a close is unavoidable, you can try the following. Override OnClosing inside the Window and set e.Cancelled to true, then call .Hide(). This should allow the window to be hidden/shown even if the user closes the window.
// Disclaimer, untested!
protected override void OnClosing(CancelEventArgs e)
{
e.Cancel = true; // cancels the window close
this.Hide(); // Programmatically hides the window
}
EDIT:
Ok I've now read your question properly ;-)
So how can i start freshly my window after closed or hide the window.
When you re-show the window using the above, it will of course be the same instance as the one that was previously hidden, hence will have the same data and state. If you want completely new contents you need to create a new Window() and call Window.Show() on that. If you hide/show as above then you'll get the window back in exactly the same state before it was hidden.
Are you using the MVVM pattern in your WPF application? If so, you could try the following. By having all the data in your ViewModel and bound to by the View (ie: no business logic or data in the code behind of the Window), then you could invoke a method on the ViewModel to reset all data when the window is shown. Your bindings will refresh and the window state will be reset. Note this will only work if you have correctly followed MVVM and bound all elements on the main form to ViewModel properties (including sub controls).
Best regards,
It really depends on the structure of your app. Since you're not maintaining state, you only need to save the actual type of window that was closed. Depending on what type of window you're using, you can assign it an ID (e.g. in its Tag property) so that it can be recognized later. You can then push this ID during the Closing event in a Stack. Then, if the user reopens it, pop the stack to get the ID and check what window that corresponds to. You can then reopen that window.
Of course, Stack is just one data structure and it may or may not be suitable for you. Using a Stack means that user can reopen all the past windows they closed, but maybe you might just want the one window instead.
Edit - basic code:
//create an enum to make it easy to recognise the type of window that was open
enum WindowType { Profile, Settings };
//create a stack to hold the list of past windows
Stack<WindowType> pastWindows = new Stack<WindowType>();
//give the window type a particular id
profileWindow.Tag = WindowType.Profile;
//open the window
profileWindow.Show();
//in the closing event, if you want the user to be able to reopen this window, push it to the stack
protected override void OnClosing(CancelEventArgs e)
{
pastWindows.Push(WindowType.Profile); //or whatever type it was
base.OnClosing(e);
}
//to reopen the last window
void ReopenLastWindow()
{
WindowType lastType = pastWindows.Pop();
switch(lastType)
{
case WindowType.Profile:
profileWindow.Show();
break;
case WindowType.Settings:
settingsWindow.Show();
break;
default:
break;
}
}
this.Opacity = 0
to "close the window"
this.Opacity = 1
to "re-open it"
A remark concerning the Hide() method: from another class, the window will in fact be considered as closed and the code will continue after a ShowDialog() method usage. Using the "Opacity" property overrides the problem.

Access windows control from Backgroundworker DoWork

my issue is the following:
I have a windows form in which I've placed a LayoutPanel, when the forms Loads, multiple controls like: textboxes and labels are being added to the LayoutPanel.
Then on a button click, I need to process the data entered by the user on those dynamically created controls. For that purpouse I use a Backgroundworker which is supposed to take those controls and read their data.
My issue es that the Backgroundworker doesn't allows me to access the control from the DoWork Method, but I need to do it that way because I'll be reporting the progress of the operations.
Here are portions of my code to clarify the concept:
private void frmMyForm_Load(object sender, EventArgs e)
{
//I add multiple controls, this one is just for example
LayoutPanel1.add(TextBox1);
....
}
private void bgwBackground_DoWork(object sender, DoWorkEventArgs e)
{
foreach (Control controlOut in LayoutPanel1.Controls)
{
//do some stuff, this one is just for example
string myString = controlOut.Name; //-> Here is the error, cant access controls from different Thread.
}
}
Setting text is simple just using a delegate, but how about getting the entire parent control to manipulate the child controls (just for getting info, I don't want to set any data, just need to Get Name, Text, stuff like that).
Hope I made myself clear, thank you all.
You can only access Windows Forms controls from the GUI thread. If you want to manipulate them from another thread, you will need to use the Control.Invoke method to pass in a delegate to execute on the GUI thread. In your situation, you should be able to do this:
private void bgwBackground_DoWork(object sender, DoWorkEventArgs e)
{
foreach (Control controlOut in LayoutPanel1.Controls)
{
this.Invoke(new MethodInvoker(delegate {
// Execute the following code on the GUI thread.
string myString = controlOut.Name;
}));
}
}
I like to define an extension method that allows me to use the cleaner lambda syntax:
// Extension method.
internal static void Invoke(this Control control, Action action) {
control.Invoke(action);
}
// Code elsewhere.
this.Invoke(() => {
string myString = controlOut.Name;
});
As you are already aware, accessing control values from any thread other than the UI thread is a big no-no. I'd say one reasonable implementation is to use a .NET synchronization mechanism, such as a WaitHandle, to suspend your background thread while the UI thread updates a thread-safe data structure of your choice.
The idea is that your background thread notifies the UI thread (via the delegate mechanism you are already familiar with) that it needs information, then waits. When the UI is finished populating the shared variable with information, it resets the WaitHandle, and the background worker resumes.
Without writing out and testing all the code, let me give you a few resources:
WaitHandle.WaitOne documentation with example usage: http://msdn.microsoft.com/en-us/library/kzy257t0.aspx
My own favorite method of invoking an event on the UI thread: http://www.notesoncode.com/articles/2009/01/24/PowerfulExtensionMethodsPart1.aspx

My application loses focus when a window is closed

I have a simple two forms, one that contains a grid and a button. When I click the button, my application starts doing a long operation. While it is working, I show another form that contains a progress bar
I open it like this:
_busyWindow.ShowDialog();
And defined
public partial class BusyWindow : DevExpress.XtraEditors.XtraForm
{
public BusyWindow()
{
InitializeComponent();
}
private void BusyWindow_FormClosing(object sender, FormClosingEventArgs e)
{
this.Hide();
e.Cancel = true; // this cancels the close event.
}
}
When the operation is finished, I hide the form like this
if (ended)
_busyWindow.Hide();
It works fine. The problem is that when I close the second form (same closing code), it also closes fine but my main GUI loses the focus. For example, if I have the Firefox opened behind the application, then the Firefox gets the focus.
This only happens when I close the second form when the busyWindow has been opened, and no when it hasn't (ie, if I open the form, I close it without clicking on the button, then the main GUI doesn't lose the focus).
Do you know what is happening or where could I try to search?
There could be two possible solutions to enable you to keep focus on your main window:
//Edited: Main Window in the below example would be the window with Grid and Button.
Since you are showing the busy window via ShowDialog() try setting the owner of the window by this: _busyWindow.ShowDialog(this);. I had earlier faced a similar problem and this worked for me. Since you specify the owner of the busyWindow, when it closes it would put the focus back on its owner,i.e. your main window
In case the above technique doesnt work (it should, as it worked for me), you could try to pass the reference of the main window to the busyWindow and then on its close set the focus of the main window. Sample:
_busyWindow.MyMainWindow = this; //MyMainWindow references mainWindow of your app
_busyWindow.ShowDialog();
And the following at the FormClosing of busyWindow:
private void BusyWindow_FormClosing(object sender, FormClosingEventArgs e)
{
this.Hide();
e.Cancel = true; // this cancels the close event.
MainWindow.Focus();
}
See if it works. The first solution should work.
Hope it helps.
Thanks & Happy Windowing!
Just set child's window Owner = null before closing it

WPF ComboBox DropDown part appears in the wrong place

I put several ComboBoxes on a XAML window. When I expand any of them, the DropDown part appears on the upper left corner of the screen.
I use Visual Studio 2008 C# Express. I don't remember this phenomenon when I used Visual Studio 2008 (Trial Version), though I use the same FrameWork (3.5).
It seems to be a bug.
Workaround:
Use Window.Show() instead with a custom logic to simulate the ShowDialog() behavior.
This appears to be a bug in WPF. In my case, I was trying to open a window in the Loaded event of another window. To get around this, I set a timer up to fire, then used a delegate to open the window (cannot open the window in a timer event because the calling thread that opens a window must be STA).
Edit - timer isn't necessary - didn't see the answer above just queue it on the dispatcher...
private delegate void DelegateOpenWindow();
private DelegateOpenWindow m_DelegateOpenWindow;
private Timer loginTimer = new Timer(200);
private void MainWindow1_Loaded(object sender, RoutedEventArgs e)
{
// create delegate used for asynchronous call
m_DelegateOpenWindow= new DelegateOpenWindow(this.OpenWindow);
// start a timer to fire off the open window.
loginTimer.Elapsed += loginTimer_Elapsed;
loginTimer.Enabled = true;
}
void loginTimer_Elapsed(object sender, ElapsedEventArgs e)
{
loginTimer.Enabled = false;
this.Dispatcher.BeginInvoke(m_DelegateOpenWindow);
}
void OpenWindow()
{
MyWindow w = new MyWindow();
w.Owner = this;
w.ShowDialog();
}
I started observing this (and other strange behavioral quirks) yesterday when I tried to "tweak" window sizes, shapes, colors, and invoke a log-on dialog from the Window.Loaded event handler. I had been doing this just fine in each of a dozen+ individual "MVVM" pattern apps. Yesterday, I decided to move this from each app's code behind into a consolidated code-behind base class, since the pre-processing had become common in all those apps. When I did, the drop-downs in two ComboBoxes in the log-in dialog suddenly appeared in the upper left corner of my screen. I seem to have "solved" it by using the following technique (your mileage may vary):
protected void WindowBaseLoadedHandler(object sender, RoutedEventArgs e)
{
...non-essential lines of code removed...
if (DataContext != null)
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
/*----------------------------------------------------------------------
* Do we have a View Model? If so, perform standard VM Initialization...
*---------------------------------------------------------------------*/
this.IsEnabled = false;
LoginDlg loginDlg = new LoginDlg();
loginDlg.ShowDialog();
if (!loginDlg.Success)
{
/*-----------------------------------
* Log on failed -- terminate app...
*----------------------------------*/
...termination logic removed...
}
this.IsEnabled = true;
}));
}
WindowBaseLoadedHandler is the Loaded event handler. LoginDlg is a WPF app with a dialog containing two ComboBoxes.
Recap: After I consolidated the code into the Loaded event handler of the base class the ComboBox's drop down lists appeared in the upper left corner of my screen. Once I wrapped the logic into the Dispatcher.BeginInvoke call, the appropriate ComboBox behavior returned with lists below the current item.
I suspect WPF needs the application to return from the Loaded event to complete the layout system's initialization. That doesn't fully explain why it worked before, but I'll have to queue up my desire to hunt that "why" down for some rainy day in the future and celebrate overcoming the latest obstacle for today.
In any event, I hope someone finds this of use.
I'm using the latest .Net 4.5 and WPF framework and I still have this problem. One thing I noticed is that it only happen when there's an attached debugger. When the debugger is not attached, everything works fine.
I had the same problem on Visual Studio 2019.
Using window.Show() can help but it can ruin your design.
The solution is to open the window asynchronously.
var yourDialog= new YourDialog();
yourDialog.Owner = this;
TaskCompletionSource<bool?> completion = new TaskCompletionSource<bool?>();
this.Dispatcher.BeginInvoke(new Action(() =>
completion.SetResult(yourDialog.ShowDialog())));
bool? result = await completion.Task;
You can also create a more elegant solution by making the extension method:
public static class AsyncWindowExtension
{
public static Task<bool?> ShowDialogAsync(this Window self)
{
if (self == null) throw new ArgumentNullException("self");
TaskCompletionSource<bool?> completion = new TaskCompletionSource<bool?>();
self.Dispatcher.BeginInvoke(new Action(() => completion.SetResult(self.ShowDialog())));
return completion.Task;
}
}
And you can use it like this:
await dlgReview.ShowDialogAsync();
It’s a bug in WPF (not the only one, I'm afraid). It happened when I opened another window in the Loaded Event, something like:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Window selectionWindow = new SelectionWindow();
bool? result = selectionWindow.ShowDialog();
if (result == true)
RecordChanged();
}
I already found a workabout.

Resources