Unload childwindow on close (silverlight mvvm) - silverlight

How may I ensure that my childwindow is unloaded when it's closed?
I am opening the childwindow from my viewmodel, but after it's been closed it still fires of events like selectionchanged on comboboxes.
The childwindow is using the same viewmodel as it's been called from, so I guess that explains why the events are being fired. The itemssources are still valid.
But when it's closed, I would like to "dispose" the childwindow for good.
I've tried to add a Closed handler like this (Default view code behind):
private void OnLaunchEditItem(ItemMessage msg)
{
var editWnd = new EditItemWindow();
editWnd.Closed += new EventHandler(editWnd_Closed);
editWnd.Show();
}
void editWnd_Closed(object sender, EventArgs e)
{
sender = null;
}
No sucesss..
So what I'm doing now is to remove the itemssource from the childwindow controls, which seems to me... not the ideal solution to the problem. It must be possible to dispose it all from memory on closing? (Childwindow "view" code-behind)
private void OKButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
combobox1.ItemsSource = null;
combobox2.ItemsSource = null;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
combobox1.ItemsSource = null;
combobox2.ItemsSource = null;
}

The messaging has a known problem that it introduces a hard link between the messenger and the recipient of a message. So if you use messaging you havee to ensure that the Messenger.Unregister method is called. In other words, when you call Register to handle a messsage make sure you call Unregister as well!
So in your view you have to register for the Unloaded event; there you then call Messenger.Unregiser(this); where this is your view.
In ViewModels you have to make sure that the Cleanup method is called to deregister the ViewModel as a message recipient.
Also see:
MVVM Light Listener not releasing / deterministic finalization for registered object? and MVVM Light Messenger executing multiple times.
Laurent is aware of this Problem but - as of now - has no solution.

Sharing ViewModels between views can lead to problems like this. That's why it's rarely done.
A ViewModel should generally not be concerned with navigation because in an ideal world it shouldn't even know what kind of view it is bound to. This includes spawing child views (ChildWindows).
I would recommend two changes to you. The first one is to create a dedicated viewmodel for your dialog. And second to decouple the navigation from the viewmodel by delegating navigation to a Controller. A controller in MVVM is usually a singleton object who's whole purpose is opening windows, dialogs etc. This can be implemented using the Event Aggregator pattern in a quite elegant fashion.

Related

WPF DataBinding with Code First Entity Framework

I am just getting familiar with WPF databinding. I've figured out most of the basics but I'm having trouble figuring out a couple of things.
First, let's say I have an object called Synth that has a collection of Banks. In turn, a Bank has a collection of Patches. I have a synth window to which I set the DataContext to a single Synth object. I have one listbox (lstBanks) that shows all the banks ({Binding Banks}) and another (lstPatches) that shows all the patches ({Binding ElementName=lstBanks, Path=SelectedItem.Patches}). This all works great. I see the applicable patches when I select a bank.
Question 1: How can I load a selected Patch into a dialog window with two-way binding, yet cancel those changes if DialogResult = false?
Right now, I have a patch dialog that receives a patch in the constructor which it sets as its DataContext, but I am only using OneWay binding. This happens on the doubleclick of lstPatches.
private void Patch_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
Models.Patch patch = (Models.Patch)((ListBoxItem)sender).DataContext;
PatchEdit p = new PatchEdit(patch);
p.Owner = this;
if (p.ShowDialog().GetValueOrDefault())
{
// Do stuff if applicable
}
}
Here is my PatchEdit constructor and OK button event:
public PatchEdit(Models.Patch Patch) : this()
{
this.DataContext = Patch;
}
private void btnOK_Click(object sender, RoutedEventArgs e)
{
Models.Patch p = (Models.Patch)DataContext;
p.Name = txtName.Text;
p.MidiProgramChangeValue = int.Parse(txtPCN.Text);
this.DialogResult = true;
this.Close();
}
If the user clicks OK on the patch dialog, that's when I set the properties from the form back to the DataContext. I wasn't sure if this was the best way to do it. I don't want to really save the changes until the user clicks OK on the main synth window. So all bank and patch edits should only remain local, and only be "locally" committed if the user clicks OK and not Cancel on the dialog.
Question 2: Once a patch is updated via the dialog, how can I get that change reflected in lstPatches?
I understand that directly navigating my models which are essential of type DBSet aren't Observable. I've seen posts regarding using an Observable collection, but doesn't this just complicate something that is supposed to be easy with WPF databinding? If it's the only way, how do I accomplish this easily using my code first models?
Question 1: Bind to a second/temporary object. If the user cancels, throw it away. If they don't, use it to update your original object. Data-binding doesn't really offer an "undo" or "reset" method.
Question 2: No, using ObservableCollection's doesn't complicate things. It is the recommended way of doing things. It is actually much harder to work without them.

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.

Is there a way to watch WPF Routed Events?

I was wondering if there's a way to watch all RoutedEvents that are raised in a WPF application. A way to write some info about the events fired to the console would be prefect to see what's going on.
I've found another way:
I've added this to the loaded handler of my UserControl.
var events = EventManager.GetRoutedEvents();
foreach (var routedEvent in events)
{
EventManager.RegisterClassHandler(typeof(myUserControl),
routedEvent,
new RoutedEventHandler(handler));
}
and this is the handler method:
internal static void handler(object sender, RoutedEventArgs e)
{
if (e.RoutedEvent.ToString() != "CommandManager.PreviewCanExecute" &&
e.RoutedEvent.ToString() != "CommandManager.CanExecute")
Console.WriteLine(e.OriginalSource+"=>"+e.RoutedEvent);
}
The CanExecute events are a bit too much in my case. If you would like to see these too, just remove the if statement.
Yes, but it requires some reflection. You're better off using a tool like Snoop that already does the hard lifting for you.
In the tab Events you can see list of events, and the element that handled it.

Getting non-UI objects to respond to WPF command bindings

I have a ViewModel class which i want to respond to the built in Refresh command whic is fired from a button but i'm not sure how to declare the CommandTarget.
Briefly, my code is as below
The ViewModel constructor and CanExecute and Executed event handlers -
public ViewModel()
{
CommandBinding binding = new CommandBinding(NavigationCommands.Refresh, CommandHandler);
binding.CanExecute += new CanExecuteRoutedEventHandler(binding_CanExecute);
binding.Executed += new ExecutedRoutedEventHandler(binding_Executed);
CommandManager.RegisterClassCommandBinding(typeof(ViewModel), binding);
}
void binding_Executed(object sender, ExecutedRoutedEventArgs e)
{
Debug.Print("Refreshing...");
}
void binding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
The markup is -
<Button Command="Refresh">refresh</Button>
Now, I've tried setting the CommandTarget on this button to {Binding Source={StaticResource ViewModel}} but i get a runtime saying Cannot convert the value in attribute 'CommandTarget' to object of type 'System.Windows.IInputElement'.
I'm new to commands so it's entirely possible I'm all kinds of wrong here. Anyhelp would be appreciated.
RoutedCommands and MVVM do not mix. RoutedCommands are tied to the visual tree and to rely on WPF's CommandBindings collection. You should implement your own ICommand classes that work with the MVVM pattern. Take a look at Prism's implementations for starters.
In my own MVVM projects, I have a couple of command implementations:
DelegateCommand. Calls provided delegates to determine whether the command can execute, and to execute the command.
ActiveAwareCommand. Works in conjunction with an interface (IActiveAware) and sends command executions to the currently active item. Multiple active aware implementations register themselves with the command, and the command automatically routes CanExecute / Execute calls to the currently active item.

How can a UserControl destroy itself?

When the user clicks on certain part of a window, I add a UserControl to the window's controls. The UserControl has a close button. What can I do in the UserControl's button handler to destroy the UserControl? There seems to be no .net analog to the Win32 DestroyWindow call, and there is no Close() method for a control. So far I have this:
private void sbClose_Click(object sender, EventArgs e)
{
Parent.Controls.Remove(this);
this.Dispose();
}
And, in case the parent needs to destroy the control, what are the steps? This is what I have so far:
Controls.Remove(control);
control.Dispose();
You're working in a managed code environment with garbage collection - there's nothing you can do to force the user control to be destroyed.
All you need to do, all you can do is to remove it from the parent and make sure there are no remaining references.
This will generally be sufficient:
private void sbClose_Click(object sender, EventArgs e)
{
Parent.Controls.Remove(this);
}
The only time you'll need more is if you tie things together with events, as you'll need to deregister those as well.
A control can't destroy itself. In terms of having a parent do it, you are on the right track. You can have the parent or another control call Dispose on it and remove all references to this control. Dereferencing the control this way will allow the GC to clean things up.

Resources