I have a ComboBox with Sex(male, female..):And I demand from user to select a value (the ComboBox has no value by default.)
<ComboBox ItemsSource="{x:Static Member=data:Sex.AllTypes}" SelectedItem="{Binding Path=Sex.Value, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True}" VerticalAlignment="Top">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Sex.Value is a Property in my Person class:
public class Person : IDataErrorInfo
{
public string this[string columnName]
{
get
{
switch (columnName)
{
case "Sex": return Sex.Value == null ? "Required field" : null;
case "Surname": return string.IsNullOrEmpty(Nachname) ? "Required field" : null;
}
}
}
public string Error
{
get
{
return null;
}
}
}
the problem is that it never enters this[string columnname].
When i try a TextBox with name, it enters this[string columnname] and everything works fine:
<TextBox Style="{StaticResource textBoxInError}" Text="{Binding Path=Surname, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True}"/>
The good way in windows is to add a value (None) into the combobox and test if the person contains the (None) value. "(None)" is a meta-option because it is not a valid value for the choice—rather it describes that the option itself isn't being used.
Correct:
(source: microsoft.com)
Incorrect:
(source: microsoft.com)
The validation doesn't work in your case because no value is selected when you want to say that no sex is selected...
I've decided to do it this way:
When user clicks on Save, the validation occur.
Then I simply check in a validation event, if a SelectedValue is null.
If it's the case, then it means that the user didn't choose any of items.
Then I warn him about this fact.
private void CanSave(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = myComboBox.SelectedValue != null;
}
Related
I have a combo box that is editable, and i have a button that is enable when SelectedReplacement that binds to the combobox is not null and disable when it is. When it's null, i would input some random text to make the button enable, the problem is it wouldn't become enable when there I input text. making the Mode TwoWay doesn't help. i assumed setting the propertychangedevent would bind the new text to SelectedReplacement, but Im wrong, so any help is appreciated.
<ComboBox ItemsSource="{Binding SelectedError.Suggestions}"
Text="{m:Binding Path=SelectedError.SelectedReplacement, Mode=TwoWay}"
IsEditable="True"
HorizontalAlignment="Stretch"/>
i also tried to get the propertychanged
private void ViewModelPropertyChanged(SpellcheckViewModel sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(sender.SelectedError.SelectedReplacement))
{
_correctCommand?.Refresh();
}
}
I try to write a demo project to meet your requirement.
Mainly, the enable state is controlled by another boolean property IsButtonEnabled in the view model, the value for that property is controlled by InputText property which is controlled by the text you input in the ComboBox control.
Here is the UI:
<StackPanel Margin="10">
<ComboBox
x:Name="cmb"
IsEditable="True"
ItemsSource="{Binding AllItems}"
TextBoxBase.TextChanged="cmb_TextChanged"
TextSearch.TextPath="Name">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox
x:Name="hiddenTextBox"
Text="{Binding InputText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Visibility="Collapsed" />
<Button
x:Name="btn"
Margin="0,10,0,0"
Content="Show message"
IsEnabled="{Binding IsButtonEnabled}" />
</StackPanel>
And here is the main logic in the view model:
public ObservableCollection<Item> AllItems
{
get { return _allItems; }
set { _allItems = value; this.RaisePropertyChanged("AllItems"); }
}
public bool IsButtonEnabled
{
get { return _isButtonEnabled; }
set { _isButtonEnabled = value; this.RaisePropertyChanged("IsButtonEnabled"); }
}
/// <summary>
/// When InputValue changed, change the enable state of the button based on the current conditions
/// </summary>
public string InputText
{
get { return _inputText; }
set
{
_inputText = value;
this.RaisePropertyChanged("InputText");
// You can control the enable state of the button easily
if (AllItems.Any(item => item.Name == value))
{
// SelectedItem is not null
IsButtonEnabled = true;
}
else if (!string.IsNullOrEmpty(value))
{
// SelectedItem is null
IsButtonEnabled = true;
}
else
{
IsButtonEnabled = false;
}
}
}
Finally, here is the project: ComboBoxDemo
I am trying to figure out a way to get the current date and set that value to "Completed On" only when Status is set to "Inactive if Status is set to "Active" then i want Completed On to be empty. I was think this could be accomplished by using a trigger event. I am also writing this app in MVVM format.
<telerik:RadGridView x:Name="dgRad" Grid.Column="0" Grid.Row="1">
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn Header="Completed On"
Width="Auto"
DataMemberBinding="{Binding EndDate, Mode=OneWay}" />
<telerik:GridViewDataColumn Header="Status"
DataMemberBinding="{Binding Status, Mode=TwoWay}"
Width="Auto" IsReadOnly="False"
IsEnabled="True" IsVisible="True">
<telerik:GridViewDataColumn.CellEditTemplate>
<DataTemplate>
<ComboBox Text="{Binding Path=Status, Mode=TwoWay}">
<ComboBoxItem>Active</ComboBoxItem>
<ComboBoxItem>Inactive</ComboBoxItem>
</ComboBox>
</DataTemplate>
</telerik:GridViewDataColumn.CellEditTemplate>
</telerik:RadGridView.Columns>
This is logic that you want at your view model level, not in the XAML.
Depending on what sort of framework you might be using, there's different ways of detecting value changes, but it would look something like this:
this.NotifyPropertyChanged += (o, e) => {
if (e.PropertyName == "Status")
{
if (this.Status == "Active") EndDate = null; //nullable DateTime?
else if (this.Status == "Inactive") EndDate = DateTime.Now;
}
};
In MVVM, your view only does things related to displaying things to the user. Logic belongs in the view model. Note that this code relates to the line item object being displayed in the grid, not your outer view model.
Hopefully this helps you get started.
This should help....
public class YourViewModel
{
public string Status{get;
set{
...
RaisePropertyChange("EndDate");
}
}
public string EndDate{
get{
return Status=="Active" ? "Completed On" + DateTime.Now : "";
}
}
}
I´m using IDataErrorInfo for validation in WPF, But when the attribute is an array I can to process the error. ( in this case int[] Position)
My code is similar to this: http://codeblitz.wordpress.com/2009/05/08/wpf-validation-made-easy-with-idataerrorinfo/
public class Customer : IDataErrorInfo
{
public string FirstName { get; set; }
public int[] Position { get; set; }
#region IDataErrorInfo Members
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string result = null;
if (columName == "FirstName")
{
// Do something
}
if (columnName == "Position")
{
// Do something
}
return result;
}
}
#endregion
}
XAML
<TextBox x:Name="tbFirstName" Grid.Row="2" Grid.Column="1" Width="50" HorizontalAlignment="left"
Validation.Error="Validation_Error" MaxLength="2"
Text="{Binding UpdateSourceTrigger=PropertyChanged,
Path=FirstName, ValidatesOnDataErrors=true, NotifyOnValidationError=true}" />
<TextBox x:Name="tbPosition1" Grid.Row="2" Grid.Column="1" Width="50" HorizontalAlignment="left"
Validation.Error="Validation_Error" MaxLength="2"
Text="{Binding UpdateSourceTrigger=PropertyChanged,
Path=Position[0], ValidatesOnDataErrors=true, NotifyOnValidationError=true}" />
I have no problem to capture "Firstname" but if I do a change in the textbox of btPosition1 the program don´t through the function to process the errors.
You bind to an item in array, not to array itself.
In this case, I think, WPF looks for IDataErrorInfo in Array class.
So you should either expose positions as separate properties (if they are finite), or use your own collection class: inherit List<> and add IDataErrorInfo implementation.
I have two controls on some panel: textbox and combobox:
<TextBox Text="{Binding ShapeName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<ComboBox SelectedItem="{Binding ActiveStageViewModel, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
I need to force idataerrorinfo validation for textbox, when I select something on combobox. How to do that?
The code from viewmodel:
string IDataErrorInfo.this[string propertyName]
{
get
{
var error = string.Empty;
if (propertyName == Expression.GetPropertyName(() => ActiveStageViewModel))
{
// TODO: Add functionality to force ShapeName property validation
return error;
}
if (propertyName == Expression.GetPropertyName(() => ShapeName))
{
error = ValidateShapeName();
}
TooltipMessage = error;
return error;
}
}
Just raise the NotifyPropertyChanged event for the property you want to validate or if you are in .NET 4.5 or Silverlight 4 use the INotityDataErrorInfo.
Worth remembering also that you can raise NotifyPropertyChanged with String.Empty property. This will force validation of all properties at that level. Can be useful at times.
I have the following code:
public event EventHandler LoadingControlVisibilityChanged;
public Visibility LoadingControlVisibility
{
get { return _LoadingControlVisibility; }
set
{
_LoadingControlVisibility = value;
if (LoadingControlVisibilityChanged != null)
LoadingControlVisibilityChanged(this, EventArgs.Empty);
}
}
<Label x:Name="loading" Visibility="{Binding Path=LoadingControlVisibility, Mode=OneWay}" Content="No Devices Detected!" FontFamily="{DynamicResource AppFont}" HorizontalAlignment="Left" Margin="110,0,0,0" FontSize="21.333" />
The first time the binding work, but after I change the LoadingControlVisibility nothing happens, after debug I notice that the event = null. Please help me solve this problem.
my text property works with no problems:
public event EventHandler UUidChanged;
public string UUid
{
get { return _uuid; }
set
{
_uuid = value;
if (UUidChanged != null) UUidChanged(this, EventArgs.Empty);
}
}
<TextBox Text="{Binding Path=UUid, Mode=OneWay}" Margin="122.48,11.26,9,0" TextWrapping="Wrap" VerticalAlignment="Top" FontSize="{DynamicResource MediumFontSize}" FontFamily="{DynamicResource AppFont}" Template="{DynamicResource TxtBoxTemplate}" Height="25" >
why is this different ?
The Binding statement will not look for the event you have defined. You must implement INotifyPropertyChanged instead.
I'm not quite certain what you're trying to accomplish. My understanding is that you are trying to bind the visibility of your label to a Property named LoadingControlVisibility that is defined in another class. If that is the case, then your path is wrong in the binding. Your binding should be as follows: Visibility="{Binding LoadingControlVisibility}"