INotifyDataErrorInfo how to force the view to getErrors()? - c#

I have a desktop app developed with WPF in .net framework 4 and trying to set up my field validation with MVVM.
I have implemented INotifyDataErrorInfo interface on my EntryClass which is being used inside MainWindowViewModel.
The interface implementation is done so my properties are not validated on propertychange inside set{} but rather after a user click 'Save' button.
public bool IsFormValid()
{
bool valid = true;
_errorHandler.ClearAllErrors();
if (BrojTransakcije==null || BrojTransakcije.Length<4)
{
_errorHandler.AddError(nameof(BrojTransakcije), "Invalid chars");
valid = false;
}
return valid;
}
And it works, after clicking'save' I first clear all of the properties ( and raise ErrorsChanged() ) check the property and if it is invalid the error is shown on the view.
private void RaiseErrorChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
public void ClearAllErrors()
{
_formGreske.Clear();
RaiseErrorChanged(string.Empty); //reset everything
}
The problem I am having is: after the user inputs the correct value, the validation passes but the error info still stays on the screen, its not updating.
If a user appends something on that input and clicks 'Save' again the error dissapears.
I have narrowed it down that the view (the binding engine I suppose) is not calling the GetErrors() method of the interface and it does not understand that the errors are cleared.
Is there a way to force the view(binding engine) to forcely GetErrors() because obviously is not doing that?
<StackPanel>
<Label Style="{StaticResource LabelTitles}"
Content="Broj transakcije"></Label>
<TextBox Style="{StaticResource InputBox}"
Text="{Binding NoviUnos.BrojTransakcije,
ValidatesOnNotifyDataErrors=True}">
</TextBox>
</StackPanel>

If you are triggering the validation on Save (eg not in your set{}) then the only time it knows to validate is when you click save again.
I would add some logic to your set; to so it validates on LostFocus personally
Also just as a general note in case you haven't heard of it the 'mvvm community toolkit' is amazing for this stuff. Using the ObservableValidator class does a lot of the hard work for you https://learn.microsoft.com/en-us/windows/communitytoolkit/mvvm/observablevalidator

Is there a way to force the view(binding engine) to forcely GetErrors() ...?
Implement INotifyPropertyChanged and raise the PropertyChanged event for the data-bound source property (BrojTransakcije). See this answer for more information.

Related

How to Force Focus Loss on a Textbox when a button is pushed

I have a pretty large wpf aplication that is built using the prism framework and MVVM concepts and if a user types a change into a textbox and presses the accept button without leaving the textbox first it causes the button action to not receive the changed values in the textbox. My viewmodel has the following button code
private DelegateCommand<string> _myButtonAction;
public ICommand MyButtonAction
{
get
{
if (_myButtonAction== null)
{
_myButtonAction= DelegateCommand<string>.FromAsyncHandler(
MyFunction,
s => true);
}
return _myButtonAction;
}
}
private Task MyFunction(string arg)
{
// this event calls a button.focus in the view
EventAggregator.GetEvent<FocusObject>().Publish("");
// this doesn't work either
Keyboard.ClearFocus();
using (new WaitCursor("my function"))
{
// do some stuff
}
return Task.FromResult<object>(null);
}
Despite trying a Keyboard.ClearFocus() and using a message event to call a MyButton.Focus(); in the view nothing seems to trigger the Textbox to lose focus once I am in this method. Does anyone else have any suggestions for how to force an update of a bunch of textboxes (lets say 100+) from a viewmodel?
The sources of data for the TextBoxes are not being told to update.
I see that you are concerned about using UpdateSourceTrigger=PropertyChanged for validation reasons. However, I believe that to be a great solution for this problem still. To keep the TextBoxes from forcing validation with every keystroke you could add a Delay to your binding.
For example:
<TextBlock Text="{Binding someProperty, UpdateSourceTrigger=PropertyChanged, Delay=100}" />

MVVM: Bring control into view

I have a Grid with a ScrollViewer around it. At the top of my ScrollViewer is a Button. On a Click on the Button, I want the ScrollViewer to scroll to a Control at the bottom of the ScrollViewer.
With the following XAML I can bring the Control into view:
<Button Grid.Row="2" Content="Some Button" Command="{Binding DoJumpCommand}" CommandParameter="{Binding ElementName=window}"/>
The Command in the ViewModel is:
if (parameter is MainWindowView)
{
var mainWindowView = parameter as MainWindowView;
mainWindowView.myJumpTarget.BringIntoView();
}
This works fine. But I'm not sure if this is clean MVVM because I pass the complete View into the ViewModel.
Is there a better way to do this?
When I first saw your question, I thought that the general solution to handling events with MVVM is to handle them in an Attached Property. However, looking again, it occurred to me that you're not actually handling any events... you just want to call a method from a UI control. So really, all you need is a way to pass a message from the view model to the view. There are many ways to do this, but my favourite way is to define a custom delegate.
First, let's create the actual delegate in the view model:
public delegate void TypeOfDelegate();
It doesn't need any input parameters, because you don't need to pass anything from the view model to the view, except a signal... your intention to scroll the ScrollViewer.
Now let's add a getter and setter:
public TypeOfDelegate DelegateProperty { get; set; }
Now let's create a method in the code behind that matches the in and out parameters of the delegate (none in your case):
public void CanBeCalledAnythingButMustMatchTheDelegateSignature()
{
if (window is MainWindowView) // Set whatever conditions you want here
{
window.myJumpTarget.BringIntoView();
}
}
Now we can set this method as one (of many) handlers for this delegate in a Loaded event handler in the view code behind:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Assumes your DataContext is correctly set to an instance of YourViewModel
YourViewModel viewModel = (YourViewModel)DataContext;
viewModel.DelegateProperty += CanBeCalledAnythingButMustMatchTheDelegateSignature;
}
Finally, let's call our delegate from the view model... this is equivalent to raising the event:
if (DelegateProperty != null) DelegateProperty(dataInstanceOfTypeYourDataType);
Note the important check for null. If the DelegateProperty is not null, then all of the attached handler methods will be called one by one. So that's it! If you want more or less parameters, just add or remove them from the delegate declaration and the handling method... simple.
So this is an MVVM way to call methods on a UI control from a view model. However, in your case it could well be argued that implementing this method would be overkill, because you could just put the BringIntoView code into a basic Click handler attached to your Button. I have supplied this answer more as a resource for future users searching for a way to actually call a UI method from a view model, but if you also chose to use it, then great!

WPF MVVM - Simple login to an application

I'm continuing to learn WPF, and focusing on MVVM at the moment and using Karl Shifflett’s "MVVM In a Box" tutorial. But have a question about sharing data between views/viewmodels and how it updates the view on the screen. p.s. I haven't covered IOC's yet.
Below is a screenshot of my MainWindow in a test application. Its split into 3 sections (views), a header, a slide panel with buttons, and the remainder as the main view of the application. The purpose of the application is simple, login to the application. On a successful login, the login view should disappear by it being replaced by a new view (i.e. OverviewScreenView), and relevant buttons on the slide of the application should become visible.
I see the application as having 2 ViewModels. One for the MainWindowView and one for the LoginView, given the MainWindow doesn't need to have commands for Login so i kept it separate.
As i haven't covered IOC's yet, I created a LoginModel class which is a singleton. It only contains one property which is "public bool LoggedIn", and an event called UserLoggedIn.
The MainWindowViewModel constructor registers to the event UserLoggedIn. Now in the LoginView , when a user clicks Login on the LoginView, it raises a command on the LoginViewModel, which in turn if a username and password is correctly entered will call the LoginModel and set LoggedIn to true. This causes the UserLoggedIn event to fire, which is handled in the MainWindowViewModel to cause the view to hide the LoginView and replace it with a different view i.e. an overview screen.
Questions
Q1. Obvious question, is logging in like this a correct use of MVVM. i.e. Flow of control is as follows. LoginView --> LoginViewViewModel --> LoginModel --> MainWindowViewModel --> MainWindowView.
Q2. Assuming the user has logged in, and the MainWindowViewModel has handled the event. How would you go about creating a new View and putting it where the LoginView was, equally how do you go about disposing of the LoginView once it is not needed. Would there be a property in the MainWindowViewModel like "UserControl currentControl", which gets set to LoginView or a OverviewScreenView.
Q3. Should the MainWindow have a LoginView set in the visual studio designer. Or should it be left blank, and programatically it realises that no one is logged in, so once the MainWindow is loaded, then it creates a LoginView and shows it on the screen.
Some code samples below if it helps with answering questions
XAML for the MainWindow
<Window x:Class="WpfApplication1.MainWindow"
xmlns:local="clr-namespace:WpfApplication1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="372" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<local:HeaderView Grid.ColumnSpan="2" />
<local:ButtonsView Grid.Row="1" Margin="6,6,3,6" />
<local:LoginView Grid.Column="1" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</Window>
MainWindowViewModel
using System;
using System.Windows.Controls;
using WpfApplication1.Infrastructure;
namespace WpfApplication1
{
public class MainWindowViewModel : ObservableObject
{
LoginModel _loginModel = LoginModel.GetInstance();
private UserControl _currentControl;
public MainWindowViewModel()
{
_loginModel.UserLoggedIn += _loginModel_UserLoggedIn;
_loginModel.UserLoggedOut += _loginModel_UserLoggedOut;
}
void _loginModel_UserLoggedOut(object sender, EventArgs e)
{
throw new NotImplementedException();
}
void _loginModel_UserLoggedIn(object sender, EventArgs e)
{
throw new NotImplementedException();
}
}
}
LoginViewViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows.Input;
using WpfApplication1.Infrastructure;
namespace WpfApplication1
{
public class LoginViewViewModel : ObservableObject
{
#region Properties
private string _username;
public string Username
{
get { return _username; }
set
{
_username = value;
RaisePropertyChanged("Username");
}
}
#endregion
#region Commands
public ICommand LoginCommand
{
get { return new RelayCommand<PasswordBox>(LoginExecute, pb => CanLoginExecute()); }
}
#endregion //Commands
#region Command Methods
Boolean CanLoginExecute()
{
return !string.IsNullOrEmpty(_username);
}
void LoginExecute(PasswordBox passwordBox)
{
string value = passwordBox.Password;
if (!CanLoginExecute()) return;
if (_username == "username" && value == "password")
{
LoginModel.GetInstance().LoggedIn = true;
}
}
#endregion
}
}
Holy long question, Batman!
Q1:
The process would work, I don't know about using the LoginModel to talk to the MainWindowViewModel however.
You could try something like LoginView -> LoginViewModel -> [SecurityContextSingleton || LoginManagerSingleton] -> MainWindowView
I know that singleton's are considered anti-patterns by some, but I find this to be easiest for situations like these. This way, the singleton class can implement the INotifyPropertyChanged interface and raise events whenever a login\out event is detected.
Implement the LoginCommand on either the LoginViewModel or the Singleton (Personally, I would probably implement this on the ViewModel to add a degree of separation between the ViewModel's and the "back-end" utility classes). This login command would call a method on the singleton to perform the login.
Q2:
In these cases, I typically have (yet another) singleton class to act as the PageManager or ViewModelManager. This class is responsible for creating, disposing and holding references to the Top-level pages or the CurrentPage (in a single-page only situation).
My ViewModelBase class also has a property to hold the current instance of the UserControl that is displaying my class, this is so I can hook the Loaded and Unloaded events. This provides me the ability to have virtual OnLoaded(), OnDisplayed() and OnClosed() methods that can be defined in the ViewModel so the page can perform loading and unloading actions.
As the MainWindowView is displaying the ViewModelManager.CurrentPage instance, once this instance changes, the Unloaded event fires, my page's Dispose method is called, and eventually GC comes in and tidy's up the rest.
Q3:
I'm not sure if I understand this one, but hopefully you just mean "Display login page when user not logged in", if this is the case, you could instruct your ViewModelToViewConverter to ignore any instructions when the user is not logged in (by checking the SecurityContext singleton) and instead only show the LoginView template, this is also helpful in cases where you want pages that only certain users have rights to see or use where you can check the security requirements before constructing the View, and replacing it with a security prompt.
Sorry for the long answer, hope this helps :)
Edit:
Also, you have misspelled "Management"
Edit for questions in comments
How would the LoginManagerSingleton talk directly to the
MainWindowView. Shouldn't everything go through the
MainWindowViewModel so that there is no code behind on the
MainWindowView
Sorry, to clarify - I don't mean the LoginManager interacts directly with the MainWindowView (as this should be just-a-view), but rather that the LoginManager just sets a CurrentUser property in response to the call that the LoginCommand makes, which in turn raises the PropertyChanged event and the MainWindowView (which is listening for changes) reacts accordingly.
The LoginManager could then call PageManager.Open(new OverviewScreen()) (or PageManager.Open("overview.screen") when you have IOC implemented) for example to redirect the user to the default screen users see once logged in.
The LoginManager is essentially the last step of the actual login process and the View just reflects this as appropriate.
Also, in typing this, it has occurred to me that rather than having a LoginManager singleton, all this could be housed in the PageManager class. Just have a Login(string, string) method, which sets the CurrentUser on successful log in.
I understand the idea of a PageManagerView, basically through a PageManagerViewModel
I wouldn't design PageManager to be of View-ViewModel design, just an ordinary house-hold singleton that implements INotifyPropertyChanged should do the trick, this way the MainWindowView can react to the changing of the CurrentPage property.
Is ViewModelBase an abstract class you created?
Yes. I use this class as the base class of all my ViewModel's.
This class contains
Properties that are used on all pages such as Title, PageKey and
OverriddenUserContext.
Common virtual methods such as PageLoaded, PageDisplayed, PageSaved and PageClosed
Implements INPC and exposes a protected OnPropertyChanged method to use to raise the PropertyChanged event
And provides skeleton commands to interact with the page such as ClosePageCommand, SavePageCommand etc.
When a logged in detected, CurrentControl is set to a new View
Personally, I would only hold the instance of the ViewModelBase that is currently being displayed. This is then referenced by the MainWindowView in a ContentControl like so: Content="{Binding Source={x:Static vm:PageManager.Current}, Path=CurrentPage}".
I also then use a converter to transform the ViewModelBase instance in to a UserControl, but this is purely optional; You could just rely on ResourceDictionary entries, but this method also allows the developer to intercept the call and display a SecurityPage or ErrorPage if required.
Then when the application starts it detects no one is logged in, and
thus creates a LoginView and sets that to be the CurrentControl.
Rather than harding it that the LoginView is displayed by default
You could design the application so that the first page that is displayed to the user is an instance of the OverviewScreen. Which, since the PageManager currently has a null CurrentUser property, the ViewModelToViewConverter would intercept this and the rather than display the OverviewScreenView UserControl, it would instead show the LoginView UserControl.
If and when the user successfully logs in, the LoginViewModel would instruct the PageManager to redirect to the original OverviewScreen instance, this time displaying correctly as the CurrentUser property is non-null.
How do people get around this limitation as you mention as do others, singletons are bad
I'm with you on this one, I like me a good singleton. However, the use of these should be limited to be used only where necessary. But they do have perfectly valid uses in my opinion, not sure if any one else wants to chime in on this matter though?
Edit 2:
Do you use a publicly available framework/set of classes for MVVM
No, I'm using a framework that I have created and refined over the last twelve months or so. The framework still follows most the MVVM guidelines, but includes some personal touches that reduces the amount of overall code required to be written.
For example, some MVVM examples out there set up their views much the same as you have; Whereas the View creates a new instance of the ViewModel inside of its ViewObject.DataContext property. This may work well for some, but doesn't allow the developer to hook certain Windows events from the ViewModel such as OnPageLoad().
OnPageLoad() in my case is called after all controls on the page have been created and have come in to view on screen, which may be instantly, within a few minutes after the constructor is called, or never at all. This is where I do most of my data loading to speed up the page loading process if that page has multiple child pages inside tabs that are not currently selected, for example.
But not only that, by creating the ViewModel in this manner increases the amount of code in each View by a minimum of three lines. This may not sound like much, but not only are these lines of code essentially the same for all views creating duplicate code, but the extra line count can add up quite quickly if you have an application that requires many Views. That, and I'm really lazy.. I didn't become a developer to type code.
What I will do in a future revision through your idea of a page
manager would be to have several views open at once like a tabcontrol,
where a page manager controls pagetabs instead of just a single
userControl. Then tabs can be selected by a separate view binded to
the page manager
In this case, the PageManager won't need to hold a direct reference to each of the open ViewModelBase classes, only those at the top-level. All other pages will be children of their parent to give you more control over the hierarchy and to allow you to trickle down Save and Close events.
If you put these in an ObservableCollection<ViewModelBase> property in the PageManager, you will only then need to create the MainWindow's TabControl so that it's ItemsSource property points to the Children property on the PageManager and have the WPF engine do the rest.
Can you expand a bit more on the ViewModelConverter
Sure, to give you an outline it would be easier to show some code.
public override object Convert(object value, SimpleConverterArguments args)
{
if (value == null)
return null;
ViewModelBase vm = value as ViewModelBase;
if (vm != null && vm.PageTemplate != null)
return vm.PageTemplate;
System.Windows.Controls.UserControl template = GetTemplateFromObject(value);
if (vm != null)
vm.PageTemplate = template;
if (template != null)
template.DataContext = value;
return template;
}
Reading through this code in sections it reads:
If value is null, return. Simple null reference check.
If the value is a ViewModelBase, and that page has already been loaded, just return that View. If you don't do this, you will be creating a new View each time the page is displayed and will cause some unexpected behaviour.
Get the page template UserControl (shown below)
Set the PageTemplate property so this instance can be hooked, and so we don't load a new instance on each pass.
Set the View DataContext to the ViewModel instance, these two lines completely replace those three lines I was talking about earlier from every view from this point on.
return the template. This will then be displayed in a ContentPresenter for the user to see.
public static System.Windows.Controls.UserControl GetTemplateFromObject(object o)
{
System.Windows.Controls.UserControl template = null;
try
{
ViewModelBase vm = o as ViewModelBase;
if (vm != null && !vm.CanUserLoad())
return new View.Core.SystemPages.SecurityPrompt(o);
Type t = convertViewModelTypeToViewType(o.GetType());
if (t != null)
template = Activator.CreateInstance(t) as System.Windows.Controls.UserControl;
if (template == null)
{
if (o is SearchablePage)
template = new View.Core.Pages.Generated.ViewList();
else if (o is MaintenancePage)
template = new View.Core.Pages.Generated.MaintenancePage(((MaintenancePage)o).EditingObject);
}
if (template == null)
throw new InvalidOperationException(string.Format("Could not generate PageTemplate object for '{0}'", vm != null && !string.IsNullOrEmpty(vm.PageKey) ? vm.PageKey : o.GetType().FullName));
}
catch (Exception ex)
{
BugReporter.ReportBug(ex);
template = new View.Core.SystemPages.ErrorPage(ex);
}
return template;
}
This is the code in the converter that does most of the grunt work, reading through the sections you can see:
Main try..catch block used to catch any class construction errors including,
Page does not exist,
Run-time error in constructor code,
And fatal errors in XAML.
convertViewModelTypeToViewType() just tries to find the View that corresponds to the ViewModel and returns the type code that it thinks it should be (this may be null).
If this is not null, create a new instance of the type.
If we fail to find a View to use, try to create the default page for that ViewModel type. I have a few additional ViewModel base classes that inherit from ViewModelBase that provide a separation of duties between the types of page's they are.
For example, a SearchablePage class will simply display a list of all objects in the system of a certain type and provide the Add, Edit, Refresh and Filter commands.
A MaintenancePage will retrieve the full object from the database, dynamically generate and position the controls for the fields that the object exposes, creates children pages based on any collection the object has and provides the Save and Delete commands to use.
If we still don't have a template to use, throw an error so the developer knows something went wrong.
In the catch block, any run-time error that occurs are displayed to the user in a friendly ErrorPage.
This all allows me to focus on only creating ViewModel classes as the application will simple display the default pages unless the View pages have been explicitly overridden by the developer for that ViewModel.

How can I apply MVVM and Commands in this specific WPF situation?

I am having trouble with the MVVM pattern and Commands in my WPF app. The problem is not so much the MVVM pattern, but more the stuff that is going on on my GUI. I'll explain the situation:
My app can DoStuff to some files. I have a class with a function DoStuff(int limit). My user user interface has the following items:
A Button DoStuffBtn to start parsing.
A TextBox LimitTxt to fill in a limit.
A CheckBox LimitChk to enabled or disable the limit.
When you would "uncheck" LimitChk, then LimitTxt.Text = "" and LimitTxt.IsEnabled = false. When you would "check" LimitChk, then LimitTxt.IsEnabled = false again, but the text remains empty until you fill something in.
I have read many tutorials on Commands in WPF and MVVM but I just can't seem to pour my case into that mold. The example I gave is actually just a small part of my UI, but I can't seem to do this nicely either.
I keep running into questions like:
Do I need two Commands for LimitChk (enable and disable) or just one (toggle)?
If I bind an int to LimitTxt, what happens if I make it empty and disable it?
Is it a clean way to just use DoStuff(Int32.Parse(LimitTxt.Text)) when DoStuffBtn is pressed?
If I use two commands on LimitChk, what happens with the CanExecute() function of ICommand that determines whether LimitChk is enabled?
So the main question is: How would the situation I described fit into a nice pattern using Commands in WPF?
Some links on WPF, Commands and MVVM i've looked at:
http://www.devx.com/DevX/Article/37893/0/page/1
http://msdn.microsoft.com/en-us/magazine/cc785480.aspx?pr=blog
http://jmorrill.hjtcentral.com/Home/tabid/428/EntryId/432/MVVM-for-Tarded-Folks-Like-Me-or-MVVM-and-What-it-Means-to-Me.aspx
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
What I understand so far is that I have to keep as much as possible out of the UI. Even stuff like UI influencing the UI. I.e. unchecking LimitChk disables LimitText. Still, I think I should keep a difference between UI related information and actions and stuff that actually has to do with the actual work that has to be done.
I think you're getting confused... you don't need any commands here, you can just use bindings.
Do I need two Commands for LimitChk (enable and disable) or just one (toggle)?
You need none. Just create a LimitEnabled property in your ViewModel, and bind the CheckBox to it (IsChecked="{Binding LimitEnabled}")
If I bind an int to LimitTxt, what happens if I make it empty and disable it?
Disabling it has no effect. If you make the TextBox empty, the binding will fail because an empty string can't be converted to an int (at least not with the default converter)
Is it a clean way to just use Parse(Int32.Parse(LimitTxt.Text)) when ParseBtn is pressed?
You don't need to. Just create a Limit property in your ViewModel, and bind the TextBox to it. You might want to add an ExceptionValidationRule to the Binding so that it highlights invalid input.
The button is not necessary, the parsing will be done automatically when the TextBox loses focus (if you use the default UpdateSourceTrigger). If you want to customize the way it's parsed, you can create a custom converter to use in the binding.
Just some high level thoughts, leaving out superfluous stuff like Color and alignment attributes, WrapPanels, etc.
Your ViewModel has a a couple properties:
public bool? LimitIsChecked { get; set; }
public bool LimitTextIsEnabled { get; set; } //to be expanded, below
public ICommand ParseCommand { get; private set; } // to be expanded, below
public string LimitValue { get; set; } // further explanation, below
Your XAML has CheckBox and TextBox definitions something like:
<CheckBox Content="Limit Enabled" IsChecked="{Binding LimitIsChecked}" />
<TextBox Text="{Binding LimitValue}" IsEnabled="{Binding LimitIsEnabled}" />
<Button Content="Parse" Command="{Binding ParseCommand}" />
You'll want to initialize ParseCommand something like this:
this.ParseCommand = new DelegateCommand<object>(parseFile);
Now, let's fill in that LimitTextIsEnabled property too:
public bool LimitTextIsEnabled {
// Explicit comparison because CheckBox.IsChecked is nullable.
get { return this.LimitIsChecked == true; }
private set { }
}
Your parseFile method would then pass the value of the LimitValue property to the logic doing the actual parsing.
I declared the LimitValue property as string here to avoid cluttering up the code with an explicit converter, or other validation code. You could choose to handle that "LimitValue is a valid int" verification/conversion in several different ways.
Of course, I haven't implemented this in its entirety, but I wanted to outline a pattern where you are not using Commands to update the state of the other widgets. Instead, bind those attributes to properties that are managed in your ViewModel.

User Interface Interaction with the MVVM

I did some googling and didn't find an answer to this puzzle.
Provided you have the following:
MySuperView
MySuperViewModel
MySuperView has two textboxes both bound to string properties on the ViewModel
and your using a DelegateCommand to bind your 'Save' button to the ViewModel using syntax such as:
ViewModel:
this.SaveOrderCommand = new DelegateCommand<object>(this.Save, this.CanSave);
View:
Command="{Binding SaveOrderCommand}"
How do you deal with UI Elements to make the User Interaction more pleasing. For example, lets say some lower level failure occurring during the save action of the DelegateCommand and you would like to trigger the tooltip of one of the TextBoxs. How would this typically occur?
I'd like to stick with as clean code-behind as possible but I am not adverse to putting UI specific code in there.
I would recommend that your ViewModel implement IDataErrorInfo so you can take advantage of validation stuff in WPF. You don't need to wait until someone clicks the save button, once the textbox gets updated it will be validated.
public string this[ColumnName]
{
if (Column == "TextProperty")
{
if(!ValidateTextProperty())
return "TextProperty is invalid";
}
}
void Save(object param)
{
if (CanSave)
{
if (string.IsNullOrEmpty(this["TextProperty"])
{
//Add Save code here
}
}
}
In your View:
<TextBox Text={Binding TextProperty, ValidateOnDataErrors="true",
UpdateSourceTrigger=PropertyChanged}/>
This will put a red box around the textbox and you can add a validation error template to the textbox style to add a tooltip see here
To show exceptions in a tooltip, I would add a property to the ViewModel that exposes the error message as a string, and bind that to your TextBox's ToolTip. Then in your Save method, you would start by setting that property to the empty string, and then doing all the real work inside a try..catch that, if an exception occurs, pushes the exception message into that property, so it automatically shows up in the ToolTip.
You would need to provide change notification for your property, either by making it a DependencyProperty or by using INotifyPropertyChanged.
Basically, you would want a create properties for your view to observe (usually through triggers) that would update your UI depending on what is happening in your code execution.

Categories