Best practices for WPF - does every GUI element need a binding? - c#

I am struggling to wrap my head around the real benefit of binding in WPF.
I have an application with a large textbox, designed for taking several hundred characters of user input. I have bound this to a "Text" string in my ViewModel. This works OK.
I also have a button with content "Submit". I need to change the content of this button once or twice, so I am doing it in the click event method in the window's code behind. I could, of course, bind the text to the ViewModel, but is it really worth it?
Should everything have a binding? What if I need to display a MessageBox? That will need some logic inside the onclick.
Should click events me as follows:
private void button_Login_Click(object sender, RoutedEventArgs e)
{
viewModel.DoSomething();
}
..where everything gets handed to the ViewModel?
I know this is a general question but I have tried my best to ask direct, answerable questions.

UI concerns are perfectly fine residing in your codebehind.
Business concerns should reside in your ViewModels. The commands and information they expose are what should be bound to elements in your UI.
Since changing the text in a button based on what the button is supposed to do is a UI concern, binding the text of the button to your ViewModel would be pointless.

I wouldn't put any code in codebehind. Create an ICommand property in your ViewModel and bind the buttons Command property to it. I use the ICommand implementation from MVVM Light (RelayCommand) but you can create your own or use one of the many other frameworks.
I'd then have a State property (ProcessStatus here) that I use a DataTrigger with to update the text on my button.
ViewModel
public ICommand LoginCommand
{
get
{
return new RelayCommand(() =>
{
ProcessStatus = Status.AUTHORIZING;
DoSomething();
});
}
}
private Status _processStatus;
public Status ProcessStatus
{
get { return _processStatus; }
set
{
if (value ==_processStatus)
return;
_processStatus= value;
RaisePropertyChanged("ProcessStatus");
}
}
View
<Button Command="{Binding LoginCommand}">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Content"
Value="Submit" />
<Setter Property="IsEnabled"
Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding ProcessStatus}"
Value="{x:Static enum:Status.AUTHORIZING}">
<Setter Property="Content"
Value="Authorizing..." />
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>

Related

WPF MVVM: Switch from one User Control to Another on button click

I've been slowly playing around with MVVM and the concept makes since. I have an application window and 2 user controls with viewModels attached to each. I want to click on a button within the first user control and get taken to the second user control.
I've looked at a few tutorials such as:
Example 1Example 2
Both those change screens from within the main window view model but I'd like to do it within the user control itself. Is it possible to either pass a command to change windows back to the main application view model, or have the user control model change the view on button click.
Edit: I figure I need to pass it as a command but I'm not sure how to pass which view I want along with it.
In MVVM, commands generally work at ViewModel level and have nothing to do with (or have any knowledge of) the View layer. To achieve what you have described, create a public property that controls the type of View you want to see. For example (using MVVM Light):
class YourViewModel : ViewModelBase
{
public string ActiveView
{
get { return _ApplicationMessage; }
//Note that this setter performs notification
set { Set(ref _ApplicationMessage, value); }
}
private RelayCommand<string> _SetViewCommand = null;
public RelayCommand<string> SetViewCommand
{
get
{
if (_SetViewCommand == null)
{
_SetViewCommand = new RelayCommand<string>((v) =>
{
ActiveView = v;
}
}
return _SetViewCommand;
}
}
}
As you see, your commands will simply manipulate the value of the public property to the appropriate value. Now your View can build on top of this property and use DataTriggers to load proper Content:
<Button Content="Normal" Command="{Binding SetViewCommand}" CommandParameter="Normal" />
<Button Content="Edit" Command="{Binding SetViewCommand}" CommandParameter="Edit" />
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ActiveView}" Value="Normal">
<Setter Property="Content">
<Setter.Value>
<UserControl1 />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ActiveView}" Value="Edit">
<Setter Property="Content">
<Setter.Value>
<UserControl2 />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Edit
Answering your comment:
view doesn't really interact with the viewModel: That's incorrect. View always interacts with the VM, it is the VM that doesn't know anything about the view. About your confusion, understand that you don't need to create one VM per user control. A VM is created against a View, not a control. So you'll probably have a single ViewModel named MainVM, exposing all the public properties that your view will bind to. Each User Control can then use only the properties that it is interested in. A trigger can then switch the view from one User Control to another when a particular property of the underlying VM is changed. The new User Control will then show correct data since it will be bound to the VM properties that it wants to work with.
Just remember that the VM is not interested in (nor have a way of) knowing how view is using it. It just needs to maintain its state correctly. View can then respond to the state changes and update itself accordingly.

Why does a OneWayToSource binding override data triggers?

I have a WPF form with multiple controls, and I want a button ('Assign') to be enabled if and only if a variety of conditions on those controls are true. Some conditions include testing whether textboxes are empty.
I initially achieved this by binding the contents of the textboxes to properties and binding the IsEnabled property of the button in the XAML:
<TextBox Name="NewName" Text="{Binding NewName}" />
(etc)
<Button Name="Assign" ... IsEnabled="{Binding Path=AssignEnabled}" />
with a corresponding method in the C# ViewModel:
public bool AssignEnabled
{
get
{
return !string.IsNullOrWhiteSpace(this.NewName) && ... (etc)
}
set
{
...
this.NotifyPropertyChanged("AssignEnabled");
...
}
}
The problem was that this caused the button to be updated when the focus was lost from the respective textbox, not whenever the text was changed. I could have used the TextChanged property on each textbox in the XAML to call code, but this seemed overcomplicated.
To fix this, I removed the binding and switched to DataTriggers like this:
<Button Name="Assign" ... >
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsChecked, ElementName=NewNameOption}" Value="true" />
<Condition Binding="{Binding Text, ElementName=NewName}" Value="{x:Static sys:String.Empty}" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="False"/>
</MultiDataTrigger>
...
(more conditions here)
...
</Style.Triggers>
</Style>
</Button.Style>
</Button>
This worked absolutely fine. The only drawback was that the unit tests I had previously written to test the IsEnabled status of the button were no longer usable (since they used the AssignEnabled property of the ViewModel which is no longer bound to the button).
To allow those unit tests to work, I needed the AssignEnabled property to reflect the IsEnabled status of the button. I expected to be able to fix this by adding a OneWayToSource binding, like this:
<Button Name="Assign" ... IsEnabled="{Binding AssignEnabled, Mode=OneWayToSource}">
with the property changed to:
public bool AssignEnabled { get; set; }
However, it seems as though this binding, even though set to OneWayToSource, overrides the DataTriggers, since the enabling/disabling of the button no longer works at all.
I know there are other options, such as using a Converter or implementing ICommand, but I would prefer to keep this simple if possible and fix the above method (or at least understand why it doesn't work). How can I access the IsEnabled property of the button without violating the ViewModel paradigm or preventing the DataTriggers from working?
You can keep your first solution and set UpdateSourceTrigger=PropertyChanged for the bindings. In this way Bindings will change instantly when text changes and not only when focus is lost. By the way this solution gives you mor flexibility as you can perform more complex testing on your fields (for exemple test email adress format).

Binding multiple elements to a single Control

I have two textbox.
Textbox A and Textbox B.
I want to bind these two text boxes to a single command button Button C.
That is if text of any the two text box is changed by the user then only the command button should get active.
Its really easy to achieve the above from Code Behind file but I was wondering that is it possible to bind a single control Button C to two elements Textbox A and Textbox B and achieve the needed through XAML.
Thanks and Regards.
If you want to enable the button if any of the two textboxes has text, you can use a MultiDataTrigger:
<TextBox x:Name="TextBoxA" />
<TextBox x:Name="TextBoxB" />
<Button x:Name="ButtonC">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Text, ElementName=TextBoxA}" Value=""/>
<Condition Binding="{Binding Text, ElementName=TextBoxB}" Value=""/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="False" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
My Suggestion is as follows:
Have TextBoxA bind to Field1 and TextBoxB bind to Field2 and
bind the Command of ButtonC to a relay command. Ensure that you implement the CanExecuteMethod
Articles here: MSDN Article on RelayEvents and here: Implementation of RelayCommand
In the canExecute method have an implementation that looks something like this:
public bool CanExecuteButtonC(object a)
{
If (!string.IsNullOrEmpty(Field1) && !string.IsNullorEmpty(Field2))
return true;
return false;
}
if the canExecute method returns false, the button will automatically be disabled, and if returns true it will be activated.
bind IsEnabled of button to a boolean property
code is not compiled or tested.
public bool IsButtonEnabled
{
get
{
return !String.IsNullorEmpty(String1) && !String.IsNullorEmpty(String2);
}
}
Make sure propertychanged for IsButtonEnabled is fired when the strings are changed
public string String1
{
//get should be here
set
{
_string1 = value;
OnPropertyChanged("IsButtonEnabled");
OnPropertyChanged("String1");
}
}

WPF programmatically setting fields as required

I am fairly new to WPF but have spent time researching WPF validation, and have not yet seen a good approach to conditional validation.
To simplify the situation greatly, let's say I have two textboxes and a submit button. The user enters a string in the first textbox. If the user enters, for example "ABC", then the second textbox should be a required field (I'd want the background to be a light blue color, to signify this), and the submit button should be disabled until that textbox is populated.
How can this be done? Is there an easy way to add/remove validations in runtime? 'DataAnnotations' (http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx) seemed like a good starting place, however I can't mark a field with the [Required] attribute, as the field won't always be required. Basically, I need something like 'Required if Field1 = 'ABC'
Thanks!
I would handle it using MVVM and here is a sample for that.
Implement IDataError Info on the class and that will implement two properties Error and this[string columnName] you can implement the second property with your binding errors that you want
public class MainViewModel:ViewModelBase,IDataErrorInfo
{
public string Error
{
}
public string this[string columnName]
{
get
{
string msg=nulll;
switch(columnName)
{
case "MyProperty": //that will be your binding property
//choose your validation logic
if(MyProperty==0||MyProperty==null)
msg="My Property is required";
break;
}
return msg;
}
}
Also Set ValidateOnErrors=True in binding of a textbox. here ColumnName is the name of the property that is changed and that has ValidateOnErrors set to true. Check here and put up the conditions and return message then you will see the errors on the tooltip when you put this style in your Resources.
<UserControl.Resources>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true" >
<Setter Property="Foreground" Value="Red"/>
<Setter Property="Background" Value="MistyRose"/>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="1.0"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
and here is a sample of the textbox
<TextBox Text="{Binding UpdateSourceTrigger=PropertyChanged, Mode=TwoWay,
Path=PropertyName,ValidatesOnDataErrors=True}" Name="textBox1">
<Validation.ErrorTemplate>
<ControlTemplate>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
I would just handle this logic in your ViewModel (assuming you're using an MVVM pattern, if not just in your code-behind).
Fire some logic on the TextChanged event for the first textbox that ultimately sets the appropriate properties. Essentially I'm saying code this validation manually. Once you start getting into more complex validation logic like this your going to start running into the limitations of the validation frameworks / declarative validation.

WPF: How do I handle an event from a Model to dynamically update xaml in MVVM

I've hit a bit of a dead end in trying to figure this one out... Using the MVVM pattern in WPF, our C# Model fires an event to say something has happened. I want to be able handle that event in my ViewModel and then either kick of a storyboard or change the visibility of a hidden panel on the current Xaml Page. This has to be handled with no Code Behind.
I can sync for the event in my ViewModel, update a property to say what the name of that event is and fire a NotifyPropertyChanged even but how do I get that to either kick off a storyboard or map to a boolean true/false on the Visibility property of my Grid? The property I bind to hs to be the event name as different grids may be shown based on different events so I need a way of mapping this to a boolean. However the ideal solution would be to kick off a storyboard. I've looked at DataTriggers but they all seem to be linked to styles and not to actual pages.
Any ideas of how I can achieve this?
Thanks!
I've used this in the past to kick off a storyboard in code-behind
Storyboard animation = (Storyboard)this.FindResource("ShowPanelStoryboard");
animation.Begin();
This code goes behind the View, not in the ViewModel. Personally, I don't mind some code behind my View providing it is only related the View. In the project I used this in, I added a listener to the VisibilityChanged event and when it got changed to Visible, I ran the storyboard.
As for showing your popup, there's a few ways. One of my favorites was just adding an IsPopupShown property to the ViewModel, binding my panel's visibility to it, and setting it to true anytime the popup should be shown. The ViewModel then handles the events that trigger the popup being shown or not.
An alternative as suggested by Dave White is to use a converter. If your value is not always true/false then you could create a converter that checks if a bound value is equal to the ConverterParameter, and return a Visibility value.
From your comment, it seems to me like what you may want to do is expose an Event property of type object in your view model. When the view model receives an event, it sets Event to an object of a type appropriate for that event. In your XAML, you have this:
<ContentControl Content="{Binding Event}"/>
and in the resource dictionary define a DataTemplate for each specific type of event you want to display. If Event is null, nothing gets displayed. If Event contains an object that you've defined a DataTemplate for, it gets displayed using that template.
Yes, you'll need to create a class for each type of event (if you don't already have one).
Another way is to implement the poor man's template selector:
<TextBlock Text="This is displayed if Foo contains 'BAR'">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Property="Foo" Value="BAR">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="This is displayed if Foo contains 'BAZ'">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Property="Foo" Value="BAZ">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
It's kind of stupidly verbose, but it's an easy way to handling a lot of mutually-exclusive display options.
Bind the Visibility property on your grid, in Xaml, to the boolean property on your ViewModel.
<Grid Visibility="{Binding Path=VisiblePropertyOnViewModel}">
Now do whatever you need in your ViewModel and set the property. As long as it does INotifyPropertyChanged or is a DependencyProperty, it will work.
I'd have to do more digging to figure out how to kick off a Storyboard, but I have no doubt it would be almost as easy. Storyboards can be kicked off by PropertyTriggers as well I believe. I'll leave this to get you started.

Categories