Caliburn.Micro - ShowDialog() how to close the dialog? - c#

EDIT:
New Information, just managed to get a logger working (I honestly had no idea cm had one!) and i'm given this message when attempting to use TryClose().
TryClose requires a parent IConductor or a view with a Close method or IsOpen property
I have been stuck on this for a number of days now, and research has turned up zero, I tried posting a question previously about this issue but it received no answers so i assume I didn't word it correctly.
I have a view and viewmodel ContentView/Model which has the following code in them:
ContentView:
<MenuItem Header="New Project" x:Name="OpenProject" cal:Message.Attach="[Event Click] = [Action NewProject()]"/>
ContentViewModel:
public void NewProject()
{
NewProjectViewModel viewModel = new NewProjectViewModel(_projectManager);
_windowManager.ShowWindow(viewModel);
//If the result is true, we have a new project, otherwise they cancelled the window.
if (viewModel.Result)
{
Project newP = new Project(0, viewModel.ProjectNo, viewModel.ProjectName, 0, 0);
_projectManager.Insert(newP);
}
}
and the viewmodel NewProjectViewModel has the following:
public void Create()
{
this.Result = true;
TryClose(true);
}
which is called in the same was as previously using an message.attach on the OK button of the dialog.
However the issue is that TryClose() always fails to close the dialog, and as i don't have the source of caliburn.micro i can't debug inside TryClose() however doing (GetView() As Window).Close() also fails because GetView() always returns null.
I'm at a complete loss as to how i can close this dialog, so any help or suggestions would be greatly appreciated.
EDIT:
Since i seem to be getting no answers on this, where as previous questions do, I'll assume i have information missing. In an attempt to understand the issue i think it may have something to with using the view first approach.
In the NewProjectView i have the following:
xmlns:cal="http://www.caliburnproject.org"
cal:Bind.Model="ShippingClient.ViewModels.NewProjectViewModel"
This is used to bind the viewmodel rather than the automatic way that is usually used, perhaps this is why GetView() returns null?

You are going to absolutely kick yourself:
Remove the cal:Bind.Model and cal:View.Model bindings...
If you are working ViewModel-First (i.e. you are creating a viewmodel and showing it using WindowManager or in a conductor) all the binding stuff that glues the viewmodel to the view is done for you by CM.
In this case you shouldn't use any View-First bindings. What you are essentially doing is newing up another instance of your VM and binding that to your view... so you have two viewmodels in the background, one wired up nicely but not bound any more, and a non-wired up instance which is bound to your view but doesn't know about the conductor.
Just remove any bindings to the viewmodel in your view and it will all work!

Related

Opening a second Window from MainWindow following MVVM and loose coupling

At first: This App and Question is for learning purpose
I'm on a new application and facing the problem that I want to open a Window when the user clicks on a Button in the MainView. In the past I'd have designed a Command which just creates the new Window and displays it
new RelayCommand((x)=>new SecondWindow().Show());
Now with this new Project I'm trying to fully decouple all classes from each other. To achieve this my App consists of 4 Assemblies (BL, COM, DAL and UI).
As in each WPF Application, the App starts with the MainWindow.xaml. The MainWindow.cs will create it's instance of MainWindowViewModel:
public ViewModel VM {get; private set;}
public class MainWindow(){
VM = new ViewModel();
InitializeComponent();
}
(which already violates loose coupling) (Any tips on how to make it better?)
My last attempt is to create an instance of my second Window inside my main window
<Window.Resources>
<local:SecondWindow x:Key="sw"/>
</Window.Resources>
and pass it as a CommandParameter to my Command
CommandParameter="{StaticResource sw}"/>
new RelayCommand((x)=> ((Window)x).Show());
This solution works but has one big disadvantage - the second window get's created immediately after the app starts - and so does it's ViewModel which starts some heavy processes (DB Connections etc.)
I've heard something abour IoC principle but I really don't know how to use it with an wpf application.
You are thinking along the right lines.... you basically have to create a List of ViewModels as your application starts up, then you can switch between them as the user presses buttons and pass the name of the ViewModel as a CommandParameter to your Command handler....
You might find this link to Rachel Lim's Blog
https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/
Also, I'm not going to post any code here coz it simply gets too complicated. So here is a download to just about the simplest example I could come up with
http://www.mediafire.com/download/3bubiq7s6xw7i73/Navigation1.rar
Download and un-RAR it (with win RAR) You will need to step though the code, figure out what its doing and how its doing it then modify it to suit your needs... Or modify your needs to suit the code.....
The example is a modification of Rachel Lim example. It simply contains Views and ViewModels, there are no Models or data. It demonstrates switching between two different Views.
UPDATE 1
With specific reference to the demo code.... Your VMs are added to a static collection of VMs (see AddViewModel function), each View ( the DataTemplate associates View with ViewModel) is selected when you click a button for example, by calling 'SelectViewCommand' which in turn sets Current_ViewModel to the selected ViewModel... the corrisponding ContentControl is then updated to display that currently selected View...
I know is confusing and very difficult to explain
When you press a button to 'change Views' you are actually changing the value of the property that your ContentControl is bound to, so you have to call the correct SelectViewCommand in the SAME instance of the class that your ContentControl is bound too...
In the demo you'll see that in the 'LogOn_View' I call
Command="{Binding DataContext.SelectViewCommand, ElementName=Base_V}"CommandParameter="Main_ViewModel"
Here I am calling the SelectViewCommand in the Base_ViewModel (x:Name="Base_V" in Base_View XAML), That's because I want to change the View that is displayed in the Base_View's 'ContentControl'
In Main_View I call
Command="{Binding SelectViewCommand}" CommandParameter="MainV1_ViewModel"
Here I am calling the SelectViewCommand in the Main_ViewModel, That's because I want to change the View displayed in the MainView's 'ContentControl'....
I typically create a WindowService class for managing window changes/dialogs in MVVM. Having "View" code in the ViewModel (i.e. Window.Show()) goes against MVVM principles. For example:
public class WindowService : IWindowService
{
public void ShowDialog<T>(ViewModelBase viewModel) where T : IApplicationDialog
{
IApplicationDialog dialog = (IApplicationDialog)Activator.CreateInstance(typeof(T));
dialog.Show();
}
}
And then your call from the ViewModel would look something like:
windowService.ShowDialog<SecondWindow>(new SecondWindowViewModel());
If you're using DI, you can pass a reference to the IoC container to the window service and create the window instances from that rather than using Activator.CreateInstance (i prefer the DI approach personally)

MVVM Violations

I want to clear some issues about MVVM violation.Becuase of this i've created a solution with some projects to demonstrate the cases.
Here's the definition (Projects) of the solution :
View (its a WPF Class Libraray and obviously it has the views)
ViewModel (its a Class Libraray and obviously it has the viewmodels )
Model (its a Class Libraray and obviously it has the models)
Domain (its a Class Libraray and it has the application dataModels )
Core (its a Class Libraray and it has the core of wpf like RelayCommnd or EventToCommand)
Application ( its a wpf application and the startup project)
ExternalCustomControl (its a wpf custom control library created by an imaginary third party company)
I Offer you to download the whole solution to understand better from
Here
First Issue :
I've an EventToCommand in the MainWindow.xaml for Closing Event of the window and attached it to MainWindowClosingCommand with the PassEventArgsToCommand set to True,then,in the MainViewModel there's a handler for the command named OnMainWindowClosing
private void OnMainWindowClosing(object parameter)
{
var arg = parameter as CancelEventArgs;
// What is the best way to show message dialog to user?
// Do i have to send message to the View to show the messageBox dialog and then the window send me the answer back to continue?
// What about IMessageBoxService? Doesn't it violates MVVM?
// Doesn't following code violates the MVVM?
// Cancel the Closing of a Window isnt a UI side duty?
arg.Cancel = true;
}
and totally whenever you want to set e.Handled or e.Cancel you face this issue.So do you know any other way that doesn't need to cast parameter as CancelEventArgs ?
Second Issue :
I've an EventToCommand in the MainWindow.xaml for PreviewMouseDown Event of the Grid and attached it to MouseClickCommand with the PassEventArgsToCommand set to True,then,in the MainViewModel there's a handler for the command named OnMouseClick:
private void OnMouseClick(object parameter)
{
// var arg = parameter as MouseButtonEventArgs;
// This is the violation of MVVM : To cast the parameter to MouseButtonEventArgs i have to add a refrence
// to PresentationCore.dll in the ViewModel Project
// The next and worse step is that in most cases we need to know the Original Source of the event
// (maybe its a StackPanel or a Label or etc) and this again vioaltes the MVVM
// So Whats the WorkAround?
}
Third Issue :
I used the ThirdParty Control(Imagine Infragistics or DevExpress or any other third party control but here as an example i created the imaginary control in my solution as the ExternalCustomControl Project) in my MainWindow Like this :
<thirdParty:ThirdPartyCustomControl Grid.Row="1"
ItemsSource="{Binding MyItemsSource,Converter={StaticResource converterKey}}" />
and ThirdPartyCustomControl has a property of type IEnumarabe<CustomControlDataModel> (CustomControlDataModel is a type that exists in the ExternalCustomControl assembly) But as you know if you want to create a property in MainViewModel for the control with the type CustomControlDataModel you have to add a refrence to ExternalCustomControl.dll in ViewModel Project and this violates MVVM so i created a type named MyDataModel and bound the ItemsSource of the control to MyItemsSource property in MainViewModel :
// If i define MyItemsSource as List<CustomControlDataModel> i have to add a refrence to ExternalCustomControl.dll
// and i think its again violate the MVVM (because ExternalCustomControl.dll is a UI Side Controls Assembly)
public List<MyDataModel> MyItemsSource { get; set; }
so i bound a property of type CustomControlDataModel to a property of type MyDataModel and of course i need a Converter :
public object Convert(object value, Type targetType, object parameter, c System.Globalization.CultureInfo culture)
{
// Imagine when the source data (MyDataModel) is huge (for example 1 milion) it (this dummy Conversion)
// affects the performance
if (value is List<MyDataModel>)
{
var result = new List<CustomControlDataModel>();
(value as List<MyDataModel>).ForEach(myVal =>
{
var custDataModel = new CustomControlDataModel();
custDataModel.ID = myVal.ID;
custDataModel.Name = myVal.Name;
custDataModel.Age = myVal.Age;
result.Add(custDataModel);
});
return result;
}
return value;
}
and the question is do you know any better way than this dummy conversion or you normally add your third party assemblies to your view and viewmodel both?
These are the issues that i've faced and i'll be appreciated if you add more if you know the other issues and share your expertise to everyone.
Upadte:
For the MessageBox Part of first issue i suggest this link
MesageBox
Thanks.
Excellent questions!
1) I personally believe you are correct, the use of a service violates MVVM. I wrote a very lengthy article on this exact topic a few weeks ago titled Implementing Dialog Boxes in MVVM. In that article I make the case for a "pure" solution to the overall problem of MVVM dialog boxes but it took 11 pages to explain how I arrived at that design. Fortunately the actual implementation is very straightforward, is similar to data templating, supports the multiple-project design you've specified and it works with 3rd party libraries. Have a read, I always appreciate objective feedback.
2) If you're using MVVM Lite then EventToCommand allows you to specify an argument converter. Here's an example where I used it to convert the window mouse move message argument to an equivalent representation in my view model:
<i:EventTrigger EventName="MouseMove">
<cmd:EventToCommand Command="{Binding ElementName=_this, Mode=OneWay, Path=MouseMoveCommand}" PassEventArgsToCommand="True" EventArgsConverter="{StaticResource MouseEventArgsConverter}" />
</i:EventTrigger>
3) If I understand your question correctly I add a reference to both the view and view model projects, at least when that is my project structure. To be perfectly honest though I usually place my view and view models in the same project e.g. MyProject.UI, with everything sorted by category folders. I saw this done during a contract I was working on for a major international firm and in practice it works really well because you typically edit a view and it's corresponding view model at the same time; having them side-by-side in the solution window really does make the whole development process easier. Obviously some purists don't like it much, but personally I don't believe that simply having them in the same project breaks MVVM provided you still adhere strictly to that architecture. I've also never had it create any problems with unit testing etc where you need to create view models only.
my closing code looks like this, i dont think that is violating mvvm
xaml
<Window>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<cmd:EventToCommand Command="{Binding ClosingCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
mainviewmodel.cs
public ICommand ClosingCommand
{
get
{
return this._closingCommand ?? (this._closingCommand = new DelegateCommand<CancelEventArgs>((args) =>
{
//i set a property in app.xaml.cs when i shut down the app there with
//Application.Current.Shutdown();
if (App.IsShutDown) return;
if (this.HasChanges)
{
var result = _msgService.Show("blup blup", "blup", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
if (result == MessageBoxResult.No)
{
args.Cancel = true;
}
}
else
{
var result = MessageBox.Show("Blup blup", "blup", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
if (result == MessageBoxResult.No)
{
args.Cancel = true;
}
}
}));
}
}
third issue (ThirdParty Control): i dont get your problem if the control need a type of collection then expose these colelction through your VM.
second issue: well is hard to say. i use something like this, and i would say is mvvm like ;)
<DataGrid x:Name="myGrd">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<Commanding:EventToCommand Command="{Binding Path=OpenCommand}"
CommandParameter="{Binding ElementName=myGrd, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
and at the end i do mvvm but always in mind to do things simple
For your second issue I think you just need to rethink what it is you are trying to achieve and why.
If you are wiring up a handler on a grid, and then making decisions based on what specific UI element of that grid was clicked in code you are probably doing things wrong. A Command called OnMouseClick is a bit of a code smell too, because that command says nothing. A command would be something like UserSelectedCommand, or GrapefuitSliceRequestedCommand ... i.e. much more specific than just a general 'something was clicked'.
You want to try and break that 'something' down into the logic for issue a clear and definitive command at that point rather than trying to work with MouseClickEventArgs and deciding in code what that means - your UI should be deciding what that means, and issuing commands to your VM.
So your individual UI elements should have the commands and binding, rather than trying to set commands at the layout UI level. If an Image is clicked that means something specific, if a row of a DataGrid is clicked that means something specific, and if a Slider is dragged that means something specific. Create your XAML so that it triggers those specific commands and don't push that responsibility up to a vague 'my whole ui was clicked and now I will use code to find out what exactly' way of thinking.

Refresh View databindings using UpdateLayout or some other alternative

I have an interface IScreenViewModel, and to simplify the problem I can
RedScreenViewModel : IScreenViewModel
GreenScreenViewModel : IScreenViewModel
etc..
This means I have a RedScreenView.xaml which makes a RedScreenViewModel instance, likewise for all subsequent color screens.
IScreenViewModel has some properties that you must implement e.g.
interface IScreenViewModel
{
public Color ScreenColor{get;set;}
}
I have a ViewmodelWrapper class which holds all viewmodels instances. ScreenViewModels, MenuViewModels etc...
Because I am using DevExpress I can't bind the DataContext directly in the Main.xaml.cs file for reasons I still don't know yet.
So in main for example.
I can't have
ScreenLabel.DataContext = viewModelWrapper.ScreenViewModel
I would have to do in main:
DataContext = viewModelWrapper;
This way the parent Window can see all child elements.
In a RedScreenView.xaml I can have something like this:
<Label Background="ScreenViewModel.ScreenColor"/>
And hopefully the data binding should look in the ViewModelWrapper find the IScreenViewModel.ScreenViewModel object and use the correct ScreenColor object using dynamic binding/polymorphism.
There are cases where a screen can have more properties, so let say in
GreenScreenViewModel along with ScreenColor property inherited, it can have its own property maybe DifferentProperty.
The problem is: I have a Factory that returns a screen object depending on what screen the user wants. It returns the correct screen object, but when it notifies the View to update itself it looks at the new object but using the wrong XAML. If that makes any sense.
I do something like this in a ViewModelWrapper method.
MainGui.ScreenWrapper.LayoutRoot.Clear() ;
MainGui.ScreenWrapper.Items.Clear() ;
MainGui.ScreenWrapper.LayoutRoot.Add(screenFactory.GetSelectedScreen("RedScreen").GetLayoutRoot()
MainGui.UpdateLayout() ;
ScreenViewModel = screenFactory.GetSelectedScreen("RedScreen").GetViewModel() ;
Ignore the fact that I called factory twice...
ScreenWrapper is LayoutGroup that holds the screens. When I swap the Views (screens) using that code I am hoping that it would use the correct bindings.
So let's say I swap from GreenScreenViewModel to RedScreenViewModel, remember GreenScreenViewModel one more property than RedScreenViewModel and in the GreenScreenView I had something like this:
<Label Content="ScreenViewModel.DifferentProperty"/>
When the swap is done and ScreenViewModel notifies that is now pointing to RedScreenViewModel it throws an exception. I am strongly assuming this is because Layout isn't being refreshed and it is still using the wrong view.
The output error in debug mode is
"Cannot find property DifferentProperty in viewModelWrapper.ScreenModel"
Which isn't right because I have already deleted THAT GreenScreenView, I updated the layout, I know there is a LayoutChanged event or something like that, so that could have been raised as well so why is it still seeing the wrong View?
How can I update ScreenWrapper.LayoutRoot to "see" the new View with a different binding code.
Heavens, I hope that was clear.
EDIT: At Michael thanks for replying. Yes there is an actual exception - "NullReferenceException" in the thirdparty dll I am using. And that is because it can't find the property. I am sure I didn't make myself clear but maybe the question should be: When deleting and inserting usercontrols from a visual tree -how can I refresh the visual tree to see new bindings? If I can refresh the visual tree it should solve my problem. UpdateLayout() doesn't work
EDIT:
At Michael thanks for replying. Yes there is an actual exception - "NullReferenceException" in the thirdparty dll I am using. And that is because it can't find the property. It throws the exception when I call OnPropertyChanged, and yeah the handler isn't null!
I am sure I didn't make myself clear but maybe the question should be:
When deleting and inserting usercontrols from a visual tree -how can I refresh the visual tree to see new bindings? If I can refresh the visual tree it should solve my problem.
UpdateLayout() doesn't work.
Firstly, you say
Because I am using DevExpress I can't bind the DataContext directly in the Main.xaml.cs file for reasons I still don't know yet.
Express should not be the problem here. You need something to bind to that will return the appropriate ViewModel. Have a look here for discussion on this subject.
Secondly, you say the error is
Cannot find property DifferentProperty in viewModelWrapper.ScreenModel
This is not necessarily a problem, and does not cause an exception. When you change bindings dynamically, INotifyPropertyChanged events fly off all over the place and there may be a period of 'uncertainty'. I am assuming your ViewModels implement INotifyPropertyChanged.
I think the key is probably looking closer at the exception, if there is one (because "Cannot find property" is a debug message, not an exception). To get some clarity you might want to turn off the binding message as described here. If there is an actual exception, please edit with the details.

How not to lose binding source updates?

Suppose I have a modal dialog with a textbox and OK/Cancel buttons. And it is built on MVVM - i.e. it has a ViewModel object with a string property that the textbox is bound to.
Say, I enter some text in the textbox and then grab my mouse and click "OK". Everything works fine: at the moment of click, the textbox loses focus, which causes the binding engine to update the ViewModel's property. I get my data, everybody's happy.
Now suppose I don't use my mouse. Instead, I just hit Enter on the keyboard. This also causes the "OK" button to "click", since it is marked as IsDefault="True". But guess what? The textbox doesn not lose focus in this case, and therefore, the binding engine remains innocently ignorant, and I don't get my data. Dang!
Another variation of the same scenario: suppose I have a data entry form right in the main window, enter some data into it, and then hit Ctrl+S for "Save". Guess what? My latest entry doesn't get saved!
This may be somewhat remedied by using UpdateSourceTrigger=PropertyChanged, but that is not always possible.
One obvious case would be the use of StringFormat with binding - the text keeps jumping back into "formatted" state as I'm trying to enter it.
And another case, which I have encountered myself, is when I have some time-consuming processing in the viewmodel's property setter, and I only want to perform it when the user is "done" entering text.
This seems like an eternal problem: I remember trying to solve it systematically from ages ago, ever since I've started working with interactive interfaces, but I've never quite succeeded. In the past, I always ended up using some sort of hacks - like, say, adding an "EnsureDataSaved" method to every "presenter" (as in "MVP") and calling it at "critical" points, or something like that...
But with all the cool technologies, as well as empty hype, of WPF, I expected they'd come up with some good solution.
At critical points, you can force the binding to push through to your view model:
var textBox = Keyboard.FocusedElement as TextBox;
BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty).UpdateSource();
Edit:
OK, since you don't want hacks we have to face the ugly truth:
In order to implement a clean view, the properties exposed by your view model should be friendly to frequent binding updates.
An analogy we can use is a text editor. If the application was a giant text box bound to a file on disk, every keystroke would result in writing the whole file. Even the concept of saving is not needed. That's perversely correct but terribly inefficient. We all immediately see that the view model needs to expose a buffer for the view to bind to and this re-introduces the concept of save and forces state handling in our view model.
Yet, we see this is still not efficient enough. For even medium-sized files the overhead of updating the whole-file buffer on every keystroke becomes unbearable. Next we expose commands in our view model to efficiently manipulate the buffer, never actually exchanging the whole buffer with the view.
So we conclude that in order to achieve efficiency and responsiveness with pure MVVM, we need to expose an efficient view model. That means that all text boxes can be bound through to properties with no ill effects. But, it also means that you have to push state down into the view model to handle that. And that's OK because a view model is not the model; it's job is it to handle the needs of the view.
It's true that we can rapidly prototype user interfaces by taking advantage of shortcuts like binding on focus changes. But binding on focus changes can have negative consequences in real applications and if so then we should simply not use it.
What is the alternative? Expose a property friendly to frequent updates. Call it the same thing as the old inefficient property was called. Implement your fast property using the slow property with logic that depends on the state of the view model. The view model gets the save command. It knows whether the fast property has been pushed through to the slow property. It can decide if when and where the slow property will be synched to the model.
But you say, haven't we just moved the hack from the view to the view model? No, we have lost some elegance and simplicity, but go back to the text editor analogy. We have to solve the problem, and it is the view model's job to solve it.
If we want to use pure MVVM and we want efficiency and responsiveness, then lame heuristics like let's avoid updating the binding source until the element loses focus won't help. They introduce as many problems as they solve. In that case, we should let the view model do its job, even if means adding complexity.
Assuming we accept it, how can we manage the complexity? We can implement a generic wrapper utility class to buffer the slow property and allow the view model to hook its get and set methods. Our utility class can auto-register for save command events to reduce the amount of boilerplate code in our view model.
If we do it right, then all the parts of the view model that were fast enough to be used with property changed binding will all still be the same, and the others that were worthy of asking the question "Is this property too slow?" will have a small amount of code to address the issue, and the view is none the wiser.
This is a tricky one and I agree a non-hack and more-or-less code free solution should be found. Here are my thoughts:
The view is responsible because it sets IsDefault to true and allows for this 'problem'
The ViewModel should not be responsible in any way to fix this it might introduce dependencies from VM to V and thus breaking the pattern.
Without adding (C#) code to the View all you can do is either change bindings (e.g. to UpdateSourceTrigger=PropertyChanged) or add code to a base class of the Button. In the base class of the button you might be able to shift focus to the button before executing the command. Still hack-ish but cleaner than adding code to the VM.
So at the moment the only 'nice' solutions I see require the view developers to stick to a rule; set the binding in a specific way or use a special button.
I would add a Click event handler for the default button. The button's event handler is executed prior the command will be called, so the data bindings can be updated by changing the focus in the event handler.
private void Button_Click(object sender, RoutedEventArgs e) {
((Control)sender).Focus();
}
However, I don't know if similar approach can be used with other shorcut keys.
Yes, I have quite some experience. WPF and Silverlight still have their pain areas. MVVM doesn't solve it all; it is not a magic bullet and the support in the frameworks is getting better but still lacks. E.g., I still find editing deep child collections a problem.
At the moment I handle these situations case by case because a lot depends on the way the individual view have to work. This is what I spend most of my time on because I generate a lot of plumbing using T4 so I have time left for these quirks.
The problem is that the TextBox's text has a default source trigger of LostFocus instead of PropertyChanged. IMHO this was a wrong default choice since it is quite unexpected and can cause all sorts of problems (such as the ones you describe).
The simplest solution would be to always explicitly use UpdateSourceTrigger=PropertyChanged (as the others suggested).
If this isn't feasible (for whatever reason), I would handle the Unloaded, Closing or Closed events and manually update the binding (as shown by Rick).
Unfortunately it seems that certain scenarios are still a bit problematic with a TextBox, so some workarounds are necessary. For example, see my question. You might want to open a Connect bug (or two) with your specific problems.
EDIT:
Pressing Ctrl+S with focus on the TextBox, I would say the behavior is correct. After all, you are executing a command. This has nothing to do with the current (keyboard) focus. The command may even depend on the focused element! You are not clicking on a button or similar, which would cause the focus to change (however, depending on the button, it may fire the same command as before).
So if you want to only update the bound Text when you lose focus from the TextBox, but at the same time you want to fire a command with the newest contents of TextBox (i.e. the changes without it having lost focus), this does not match up. So either you have to change your binding to PropertyChanged, or manually update the binding.
EDIT #2:
As for your two cases why you cannot always use PropertyChanged:
What precisely are you doing with StringFormat? In all my UI work so far I use StringFormat to reformat data I am getting from the ViewModel. However, I am not sure how using StringFormat with data which is then again edited by the user should work. I am guessing you want to format the text for display, and then "unformat" the text the user enters for further processing in your ViewModel. From your description, it seems it isn't "unformatted" correctly all the time.
Open a Connect bug where it isn't working as it should.
Write your own ValueConverter which you use in the binding.
Have a separate property with the last "valid" value and use that value in your ViewModel; only update it once you get another "valid" value from the property you use in databinding.
If you have a long-running property setter (ex. a "validate" step), I would do the long-running part in a separate method (getters and setters should normally be relatively "fast"). Then run that method in a worker thread/threadpool/BackgroundWorker (make it abortable so you can restart it with a new value once the user enters more data) or similar.
What you think about proxy command and KeyBinding to ENTER key?
EDITED:
There we have one utility command (like converter), which requires knowledge about concrete view. This command can be reused for any dialog with same bug. And you add this functionality/hack only in view where this bug exists, and VM will be clear.
VM creates to adapt business to view and must provide some specific functionality like data conversion, UI commands, additional/helper fields, notifications and hacks/workarounds. And if we have leaks between levels in MVVM we have problems with: high connectivity, code reuse, unit testing for VM, pain code.
Usage in xaml (no IsDefault on Button):
<Window.Resources>
<model:ButtonProxyCommand x:Key="proxyCommand"/>
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="Enter"
Command="{Binding Source={StaticResource proxyCommand}, Path=Instance}"
CommandParameter="{Binding ElementName=_okBtn}"/>
</Window.InputBindings>
<StackPanel>
<TextBox>
<TextBox.Text>
<Binding Path="Text"></Binding>
</TextBox.Text>
</TextBox>
<Button Name="_okBtn" Command="{Binding Command}">Ok</Button>
</StackPanel>
There used special proxy command, which receive element (CommandParameter) to move focus and execute. But this class requires ButtonBase for CommandParameter:
public class ButtonProxyCommand : ICommand
{
public bool CanExecute(object parameter)
{
var btn = parameter as ButtonBase;
if (btn == null || btn.Command == null)
return false;
return btn.Command.CanExecute(btn.CommandParameter);
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (parameter == null)
return;
var btn = parameter as ButtonBase;
if (btn == null || btn.Command == null)
return;
Action a = () => btn.Focus();
var op = Dispatcher.CurrentDispatcher.BeginInvoke(a);
op.Wait();
btn.Command.Execute(btn.CommandParameter);
}
private static ButtonProxyCommand _instance = null;
public static ButtonProxyCommand Instance
{
get
{
if (_instance == null)
_instance = new ButtonProxyCommand();
return _instance;
}
}
}
This is just idea, not complete solution.

Childwindows in MVVM

I'm having a problem understanding something about MVVM. My application relies on dialogs for certain things. The question is, where should these childwindows originate from? According to MVVM, viewmodels should contain only businesslogic and have zero actual knowledge about UI. However, what other place should I call my childwindows from, considering they're UI elements?
Doesn't this create tight coupling between elements?
Since you tagged the question with Prism, I'll suggest the way I've done it in the past using Prism. Have the IEventAggregator injected into your ViewModel, and then when you want to pop open the dialog, publish a "ShowDialogEvent" or something like that. Then, have another Module called "DialogModule" or whatever, which upon initialization subscribes to that event, and shows the dialog. Furthermore, if you want to pass data back to the original ViewModel, have the ViewModel of the dialog publish a "DialogCloseEvent" or something like that with a payload of the data you need. You can then subscribe to that event back in your main ViewModel.
See Handling Dialogs in WPF with MVVM
In the past, I have accomplished this by using Unity to resolve a custom interface that has a Show() method and a completed event. Then in the ViewModel I would call IScreen screen = container.Resolve<IScreen>(Resources.EditorWindowKey); and then just call screen.Show();.
The big advantage of this is that I can then just simply change my Unity configuration to remove the view when I'm testing my VM's.
The primary route I've been using to do this is to create a command inside your View layer. That command object accepts a parameter that is the ViewModel object that you want to display. The command then finds the appropriate ChildWindow, creates it and displays it with the parameter set as the content or however you will set it up. This way you can just bind a button's command property to that command, and its commandparameter to the object you want to show in the popup and your ViewModel objects never have to care how it's being displayed.
Prompting for user input (like saving a dirty file or something) doesn't work in this scheme. But for simple popups where you manipulate some data and then move on, this works very well.
The ViewModel sample application of the WPF Application Framework (WAF) demonstrates how to show a Modal Dialog.
I would suggest to use a controller in this scenario, say DI'ed dialogController backed up with a dialog shell. The source viewmodel(ie from where the request to open a dialog is originating) will make a call to dialogController.ShowDialog(<<ViewNameToHostInRegion>>,<<RegionName>>).
In Order to transfer the data to and from the dialog and sourceview you can use MessageBus. So essentially when you invoke the ShowDialog() you populate the messagebus, and when the close command of target View(The view hosted in Dialog shell) invoked - say in "Select" button -- Let the target view add/update the messagebus. So that source view model can work with updated data.
It has got many advantages :
1) Your source view works with dialog controller as BlackBox. ie it doesnt aware of what exactly the Dialog view is doing.
2) The view is being hosted in Dialog Shell -- so you can reuse the dialog again and again
3) Unit testing of source view is limited to test the actual functionality of the current viewmodel, and not to test the dialog view\view model.
One heads-up which I came across with this one is, while creating the test cases you may need to write testable Dialog controller which do not show the actual dialog while running the testcases in bunch. So you will need to write a TestableDialogController in which ShowDialog does nothing (Apart from deriving from IDialogController and provide blank implementation of ShowDialog().
Following is the psudeo code :
LocalMessageBus.AddMessage(<MessageKey>,<MessageActualContentAsObject>);
dialogController.ShowDialog(<TargetViewName_SayEmployeeList>);
Employee selectedEmployee = LocalMessageBus.GetMessage(<MessageKey>) as Employee;
if (selectedEmployee != null)
{
//doSomework with selected employee
}

Categories

Resources