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.
Related
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.
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}" />
I'm thinking about the best way to validate user input.
Let's imagine some TextBoxes, CheckBoxes or whatever .NET control you please, where the user input has to be validated as OK or NOK. As soon as the user's filled up all required fields he submits via a button.
Now I have to know which fields were previously confirmed as OK and which as NOK. By now I've always handled such cases by declaring a global bool variable for every control to tell me so. But I don't like that...
I'm pretty sure there must be another way! What I would like to do is expanding these .NET controls with a OK or NOK property called status or similar. Can you do that? And if so how do you do it? Is something like that already existing?
Thank you for your response!
You have some useful features in windows forms to perform validation and show error messages including:
IDataErrorInfo Interface
Validating Event of Controls
ErrorProvider Component
ValidateChildren Method and AutoValidate Property of Form
Using above options:
You can perform validation when you are using data-binding to model classes.
You van perform validation when you don't use data-binding.
You can show error messages and an error icon near the controls which are in invalid states.
You can decide to prevent the focus change from invalid controls or let the focus change.
You can show a validation summary for your form.
You can also apply DataAnnotations Validations in Windows Forms
IDataErrorInfo Interface
In cases which you have some model classes, the best fit for validation and providing error messages in windows forms is implementing IDataErrorInfo. It's supported by data-binding mechanisms and some windows forms control like DataGridView and ErrorProvider.
To keep things simple you can write validation rules in your class and return error messages using IDataErrorInfo properties. Even if you want to apply a more advanced scenario like using validation engines, at last it's better to implement IDataErrorInfo to gain most consistency with widows forms.
You will use an ErrorProvider to show error messages. It's enough to bind it to your data source and it shows errors automatically.
Validating Event of Controls
In cases that you don't have model classes and all validations should be done against controls, the best option is using Validating event of controls. There you can set e.Cancel = true to set the control state as invalid. Then you can prevent focus change or use the state of control in getting validation summary.
In this case you will use an ErrorProvider to show errors. It's enough to set an error for a control in Validating event this way: errorProvider1.SetError(control1, "Some Error") or you can set an empty error message to remove validation error.
ErrorProvider Component
In both cases when you use databinding or when you use Validating event, as mentioned above, ErrorProvider shows and error icon with a tooltip that shows error message for you near the controls. (DataGridView uses its own mechanism to show errors on rows and cells, without using an ErrorProvider.)
You can also use the component to get a validation summary for your form using GetError method of the component which return the error message of each control.
ValidateChildren Method and AutoValidate Property of Form
You can use ValidateChildren method of form or your container control to check if there is a validation error for your controls or not.
Based on the value of AutoValidate property of your form, it prevents focus change or let the focus change from invalid controls.
Save the names of your controls to be validated into an array and then just loop through them. You can also set a validation function onto them, if you want to.
var elements = new[] {
new { Control = textBox1 },
new { Control = textBox2 }
};
foreach (var elem in elements)
{
elem.Control.BackColor = string.IsNullOrWhiteSpace(elem.Control.Text) ? Color.Yellow : Color.White;
}
Wrap your Elem array into class objects to add a "ok" property.
It really depends how deep you want to delve into that rabbit hole...
You need to decide on the validation statuses - if it's simply a case of Yes/No, then Boolean/bool will suffice, otherwise you should consider creating an enumeration to hold your validation statuses.
You will need to decide whether you want to extend the controls that require validation, or just use the control's Tag property to store the validation status (personally I think that using Tag to do this is hideous).
An Example:
// Provides your validation statuses.
public enum ControlValidation
{
Ok,
NotOk
}
// Provides a contract whereby your controls implement a validation property, indicating their status.
public interface IValidationControl
{
ControlValidation ValidationStatus { get; private set; }
}
// An example of the interface implementation...
public class TextBox : System.Windows.Forms.TextBox, IValidationControl
{
public ControlValidation ValidationStatus { get; private set; }
...
protected override void OnTextChanged(EventArgs e)
{
ValidationStatus = ControlValidation.Ok;
}
}
All winforms components have a "spare" property which you can use: Tag. It's an object and you can assign whatever to it: it's not used for anything by the framework, and it's useful for cases like this.
If this is going to be generalized, you can just derive your controls and add your properties, but for a one-time single-property, Tag could perfectly work.
// OK
myTextBox.Tag = true;
// NOK
myTextBox.Tag = false;
// Undefined
myTextBox.Tag = null;
To check:
if(myTextBox.Tag is bool)
{
var isOk = (bool)myTextBox.Tag;
if(isOk)
{
// It's OK
} else {
// It's NOK
}
} else {
// It's undefined
}
All that said, I use Tag for simple things and simple logics. If you plan to have more properties or it's a generalized thing... either use the validation mechanisms explained in the other answers, or derive your controls:
public class MyTextBox : System.Windows.Forms.TextBox
{
public bool ValidationOK { get; set; }
}
And change the controls to MyTextBox (if you already have them, open the designer.cs file and change all instances of System.Windows.Forms.TextBox to <yourNamespace>.MyTextBox), etc.
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!
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.