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.
Related
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)
I'm building a cross platform app in Xamarin using MvvmCross, I've declared my ViewModels in my core and am working on my Windows 8 project first, which thanks to Stuart Lodge who answered me previously with this link, I can bind a Page's Loaded event as such (using the Interactivity dll):
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding MyICommandThatShouldHandleLoaded}" />
</i:EventTrigger>
</i:Interaction.Triggers>
How can I do the same using MvvmCross in Android?
Thanks!
Can you tell what you are trying to accomplish on the loaded event?
When trying to implement a view-model, usually I don't want tothink in terms of the UI.
The UI can work differently depending on the platform.
One way to think when implementing a view-model is think that you will want to use the view-models for testing, with no UI. In this case, your app should be able to run with no UI attached. If you need user input (like Username and Password), the test code you write is setting the properties of the view model, similar to how the data-binding is doing it when the UI is bound to these properties:
loginViewModel.Username = "admin";
loginViewModel.Password = "12345";
For example, In the link you gave, I am not sure why would be needed to know about the mainwindow loaded event to show the login screen.
The MainViewModel coressponding to the main window could call
pulic class MainViewModel : MvxViewModel
{
public void Init()
{
ShowViewModel<LoginViewModel>();
}
}
This is just an example, it might not be so good, but I am just trying to show a different way.
Remember that from a view-model perspective, the ShowViewModel doesn't mean too much.
The view-model just calls to show another view-model, something which is 'somehow' connected to a view.
It can mean showing a popup, or navigating to another page / window, etc. The view-model doesn't need to know. This is mainly because the UI can work differently on each platform, or the UI requirements can be different on each platform (because the UI patterns can be different) .
MvvmCross has a nice way to detour the default ShowViewModel behavior (by writing a platform specific custom presenter). Basically, you get in control of how a ShowViewModel request for a view-model type is executed on the platform.
Currently I have 2 ViewModels in my project. MainViewModel is currently bound to my WPF window, so I have a command in my other ViewModel (AdventurerViewModel) that doesn't fire anymore. Is there a way to have that command fire without the ViewModel being bound or do all of my commands need to be in MainViewModel? And is there a good way to organize commands that might help me avoid this situation in the future? Any advice would be greatly appreciated.
EDIT: A bit of code
From my XAML
Button Grid.Column="3" Grid.Row="1" Content="IncreaseLevel" Command="{Binding IncreaseLevel}"
AdventurerViewModel.cs
public ICommand IncreaseLevel { get { return new RelayCommand(IncreaseLevelExecute, CanIncreaseLevelExecute); } }
private bool CanIncreaseLevelExecute()
{
return true;
}
private void IncreaseLevelExecute()
{
Level++;
}
First make sure you split the ViewModels correctly and have the commands in the correct ViewModels.
After that it might still be possible that you want to notify other ViewModels of changes in a current ViewModel. In that case you could have a look at a messaging system between ViewModels so a ViewModel can subscribe to a specific event and respond to it.
Use one ViewModel per View. If you use a Window then you should use one ViewModel. Right now in my project a have a Window which has 2 user controls in it, and each UserControl has its own ViewModel plus the ViewModel for the Window. I could put everything in one ViewModel but it is cleaner this way, but for your example I recomend you put everything in one ViewModel. It realy depends on situations or project requirements.
I am making a windows phone 7 and trying to do it using MVVM. I would like to keep my view model as clean as possible but I am unsure on how to make a dialog box. I am using MVVM light and I know they have Messaging system or something but not really sure how to use it.
I would like to use Guide.BeginShowMessageBox as this seems to give more features than the standard dialog box.
How can I do this without breaking the MVVM pattern. AS when I load up the view I want to have a loaded trigger to be triggered and then check some conditions. If conditions are met show the Dialog.
// Vm
public RelayCommand MainPageLoaded
{
get
{
if (!NetworkInterface.GetIsNetworkAvailable())
{
// breaks MVVM now as have view code in viewmodel. Need to take out somehow
Guide.BeginShowMessageBox("Test", "Test network", new List<string>() { "Yes", "No" }, 0, MessageBoxIcon.Warning, asyncResult =>
{
int? returned = Guide.EndShowMessageBox(asyncResult);
// if yes then work offline mode? Maybe another property in ViewModel will get set to say offline mode?
}, null);
}
return null;
}
set
{
// Not sure what to put here.
}
}
// View
<i:Interaction.Triggers>
<i:EventTrigger>
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding MainPageLoaded}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Edit
Another problem I am having is. I have a list that is bound to some data that is stored in this property
public ObservableCollection<ContactGroup> ContactGroups { get; set; }
then on tap I have a relaycommand that should be triggered
public ICommand GroupContactTapped
{
get
{
return new RelayCommand<GestureEventArgs>(e =>
{
var selectedTextBlock = e.OriginalSource as TextBlock;
MessageBox.Show(selectedTextBlock.Tag.ToString());
});
}
}
Yet I don't know how to find which object was "tapped" without casting the source to a textblock.
Assuming that you have one mainpage/view that hosts all the other views, like a mainwindow:
I send a message event from the viewmodels, and the dialog box is handled in the code behind of the main window. This is the only codebehind I have in my project so I find it acceptable that the rest of the project can be strictly MVVM, with this one exception.
I send the message with the following (converted from VB so it might need work):
object message = new DialogMessage("YourMessage", YourFunctionThatHandlesCallback) {
Button = MessageBoxButton.YesNo,
Caption = "Caption Goes Here"
};
Messenger.Default.Send(message);
I register for the dialog box with the following in the main page code behind:
Partial Public Class MainWindow
Inherits Window
Public Sub New()
InitializeComponent()
''single initialization of messanger for catching message box
Messenger.[Default].Register(Of DialogMessage)(Me, Sub(msg)
Dim result = MessageBox.Show(msg.Content, msg.Caption, msg.Button, MessageBoxImage.Warning)
''Send callback
msg.ProcessCallback(result)
End Sub)
End Sub
End Class
I could not succesfully convert the C# lambda so I had to leave it in VB. Hope this helps
There is a MessageBoxService in the Cimbalino Phone Windows Toolkit!
You can use that in a MVVM architecture.
What it truly means to follow "the MVVM pattern" is a very subjective thing.
For instance, some people will say you shouldn't show/launch a messagebox (of any type) from the VM, while others will say this is fine.
As with any ambiguity, you'll need to balance adherence to a pattern, with what's most appropriate for a specific project, with what's appropriate for the people developing and maintaining the code base.
In terms of MvvmLight, the messaging system it uses is for communicating from a viewmodel to either another viewmodel or a view, not for displaying messages to the user.
If you are going to use Guide.BeginShowMessageBox, particularly from a viewmodel, beware that it is non-blocking. If you want it to behave like a "regular" MessageBox you'll need to use it with a ManualResetEvent so that it's not possible to continue to interact with the app while the messagebox is displayed.
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.