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.
Related
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>
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).
I have a strange problem, I'm currently trying to styling a SingleUpDown Control (from Extended WPF Toolkit)
This is the current style that does not work:
<xctk:SingleUpDown>
<xctk:SingleUpDown.Style>
<Style TargetType="xctk:SingleUpDown">
<Style.Triggers>
<DataTrigger Binding="{Binding _ThresoldLocked}" Value="True">
<Setter Property="Value" Value="2"/>
</DataTrigger>
</Style.Triggers>
</Style>
</xctk:SingleUpDown.Style>
</xctk:SingleUpDown>
While these two examples work like a charm:
<xctk:SingleUpDown>
<xctk:SingleUpDown.Style>
<Style TargetType="xctk:SingleUpDown">
<Style.Triggers>
<DataTrigger Binding="{Binding _ThresoldLocked}" Value="True">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</xctk:SingleUpDown.Style>
</xctk:SingleUpDown>
Or
<xctk:SingleUpDown>
<xctk:SingleUpDown.Style>
<Style TargetType="xctk:SingleUpDown">
<Setter Property="Value" Value="2"/>
</Style>
</xctk:SingleUpDown.Style>
</xctk:SingleUpDown>
What I'm doing wrong with the first Style?
There's no Error in the Output window..
EDIT:
This is my minimalist example:
http://i.stack.imgur.com/EzxBP.png
Usually when it comes to styles and triggers people are not aware of precedence, namely that local values among others completely override styles. Given the fact that you did not set any properties on the controls all styles should work.
So for example if you set the Value on your control (<xctk:SingleUpDown Value="0">...) the trigger does nothing, you would then move defaults into a Setter.
<xctk:SingleUpDown>
<xctk:SingleUpDown.Style>
<Style TargetType="xctk:SingleUpDown">
<Setter Property="Value" Value="<default here>"/>
<Style.Triggers>
<DataTrigger Binding="{Binding _ThresoldLocked}" Value="True">
<Setter Property="Value" Value="2"/>
</DataTrigger>
</Style.Triggers>
</Style>
</xctk:SingleUpDown.Style>
</xctk:SingleUpDown>
Edit:
I made an observation about the control's behaviour. If you have a setup as shown above the Trigger will work, so the default setter is kind of necessary here.
Also you should note that manipulating the value will probably set it locally, thus overriding any further effects from the trigger. You can get around this by using animations instead of triggers as they have the highest precedence, however this may then void any manual manipulation.
Maybe you should move your logic away from the UI and just bind the Value directly to a property on your view-model/model, e.g.
private bool _ThresholdLocked;
public bool ThresholdLocked
{
get { return _ThresholdLocked; }
set
{
if (value != _ThresholdLocked)
{
_ThresholdLocked= value;
OnPropertyChanged("ThresholdLocked");
OnPropertyChanged("Value"); //Value is also affected
}
}
}
private float _Value;
public float Value
{
get
{
if (ThresholdLocked)
return 2.0f;
return _Value;
}
set
{
if (value != _Value)
{
_Value = value;
OnPropertyChanged("Value");
}
}
}
I using Extended WPF Toolkit, but I don't have SingleUpDown there, but you probably talking about different version than this is: http://wpftoolkit.codeplex.com/. I have there DoubleUpDown and your nonfunctional style works for me.
I'm writing an IM program on Windows Phone 8. And I am currently dealing with the UI for chatting.
I want to create a ListBox that holds all the "chat bubbles" (like those in iPhone). The incoming messages appear on the left-hand side, and outgoing messages on the right-hand side. Like this:
So obviously, I need to set differnt alignment for each item.
I was trying to solve this by wrapping the bubble in a large Grid that expands all the space in the ItemsPanel, and align the chat bubble to the right (the parent of the chat bubble is the large Grid). But that didn't work because a grid in ItemsPanel won't fill up all the spaces automatically. And then I went for searching "how to fill up all spaces in ItemsPanel" and no luck.
So, I think the only way to do this is to set different ItemsPanelTemplate for each ItemsPanel, to either "Right" or "Left".
Please, help me.. Thank you!
So how do you create a selector for different ItemsPanelTemplate?
You just need to customize the itemContainer :
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAnswer}" Value="True">
<Setter Property="HorizontalAlignment" Value="Right"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
In case you dont have style triggers, you can use binding and a bool to HorizontalAlignment converter :
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="HorizontalAlignment" Value="{Binding IsAnswer,Converter={StaticResource AlignmentConverter}}"/>
</Style>
</ItemsControl.ItemContainerStyle>
you only need 2 View's for your messages aka DataTemplate
one for MyMsg and one for Answer both inhire from the Message class or interface (your decision) lets call it Msg
so you can set your ItemsSource to List<Msg> or ObservableCollection<Msg> and you are done
I have a very simple object called CellData. Is defined as:
public sealed class CellData
{
internal string DisplayText
{
get;
set;
}
public string Color
{
get;
set;
}
public override string ToString()
{
return this.DisplayText;
}
}
I can get it to display using the WPF toolkit DataGrid just fine. However, I want to be able to change the background color of each cell based on what data is in the cell. I'm having trouble understanding what type of binding I need to do because I can't see to get to the CellData object in my DataTrigger. I have tried the following, and several other variations but I can't get it to work:
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=(CellData).Color, Mode=OneWay}" Value="1">
<Setter Property="Background" Value="Red" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
I am pretty new to XAML databidinding so any suggestions would be greatly appreciated. Thank you.
I'm guessing you have a RowData object that contains several CellData objects, that you have bound the ItemsSource of the DataGrid to a list of RowData objects, and that you are using DataGridTextColumns or other DataGridBoundColumns bound to the properties on RowData, maybe just by using AutoGenerateColumns="True".
The problem is that the DataContext for the cell is actually the RowData, not the CellData. The Binding is only used for the Text property of the TextBlock and TextBox. This is useful if you want to have the triggers based on other properties of the RowData object, but makes it difficult in scenarios like yours where you have a rich data structure for the cell data.
If you are creating the columns explicitly, you can just use the property for the column again in the trigger:
<DataGridTextColumn Binding="{Binding Foo}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<!-- DataContext is the row, so start
binding path with Foo property -->
<DataTrigger Binding="{Binding Foo.Color}" Value="1">
<Setter Property="Background" Value="Red" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
Unfortunately, this won't let you share the style between columns since it is specific to the column name. If you want to do that, you may need to create a custom DataGridColumn subclass that has a property of type Binding that it applies to the DataContext property of the object returned by GenerateElement and GenerateEditingElement.
(Using a Binding with RelativeSource of Self as you did in your sample gives you the element in the visual tree rather than its DataContext, which won't help you get to the CellData object.)
You can use a ValueConverter: create a binding between the cell's background color and the Color property of your object, then add a ValueConverter to ensure that your data is properly converter to the object needed to set the background.