Apply conversion to all elements in a ComboBox drop down menu - c#

I am trying to change the text only for contents of all items in a ComboBox based on a specific property in the ViewModel. I’ve created a DataTemplate with the Binding values as SelectedValue and the specific property I want to base the conversion on SomeProperty:
<ComboBox ItemsSource="{Binding Path=ChannelValues}"
SelectedValue="{Binding Path=Channel, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource ResourceKey=ChannelNumConverter}">
<Binding Path="SelectedValue"
RelativeSource="{RelativeSource AncestorType={x:Type ComboBox}}" />
<Binding Path="DataContext.SomeProperty"
ElementName="DataContextView" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This appears to work, expect all the values in the drop down get changed to the translated SelectedValue. I’ve tried replacing SelectedValue with Text, but that doesn’t work either. Is there a way to apply this conversion to all values in the drop down (again, only changing the displayed values, not the underlying data)?
Update - ViewModel
// Populate somewhere with values
private ObservableCollection<ushort> mChannelValues = new ObservableCollection<ushort>();
public ObservableCollection<ushort> ChannelValues
{
get
{
return mChannelValues;
}
}
private ushort mChannelNum;
public ushort Channel
{
get
{
return mChannelNum;
}
set
{
if (mChannelNum != value)
{
mChannelNum = value;
OnPropertyChanged(new PropertyChangedEventArgs("Channel"));
}
}
}
private ushort mSomeProperty;
public ushort SomeProperty
{
get
{
return mSomeProperty;
}
set
{
if (mSomeProperty!= value)
{
mSomeProperty= value;
OnPropertyChanged(new PropertyChangedEventArgs("SomeProperty"));
}
}
}
Update 2 - Simple Converter
public object Convert(
object[] values,
Type targetType,
object parameter,
CultureInfo culture)
{
if (targetType != typeof(string))
throw new InvalidOperationException("The target must be a string");
if ((values[0] != null) && (!(values[0] is ushort)))
throw new InvalidOperationException("The channel must be an short");
if ((values[1] != null) && (!(values[1] is ushort)))
throw new InvalidOperationException("The some property must be a ushort");
ushort ushort_val = ushort.Parse((string)values[0]);
ushort ushort_some_property = ushort.Parse((string)values[1]);
switch (ushort_some_property)
{
case 0:
return (ushort_val + 1).ToString();
case 1:
return (ushort_val + 7).ToString();
case 2:
return (ushort_val + 2).ToString();
default:
return ushort_val.ToString();
}
}

Instead of using a MultiBinding, you could use SomeProperty as a ConverterParameter
<TextBlock Text="{Binding Converter={StaticResource ResourceKey=ChannelNumConverter}, ConverterParameter={Binding SomeProperty}}"/>
In converter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var someProperty = parameter as SomeType;
...

The problem is in fact that your itemTemplate is applied to ALL! Items in the combobox, whose converter actually processes the currently selected item and someproperty leading to equal values in all items.
The approach at this point is not the problem. You just have to bind the current text's value in the viewmodel instead of the selected value.
This will combine two values from your viewmodel into the result displayed in the textbox without any recursive updates.

Finally figured out how to do this. Below is the xaml to apply the converter to all elements in the drop down list:
<ComboBox ItemsSource="{Binding Path=ChannelValues}"
SelectedValue="{Binding Path=Channel, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource ResourceKey=ChannelNumConverter}">
<Binding />
<Binding Path="DataContext.SomeProperty"
RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType=local:DataContextView}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Related

Bind custom class with Validation in WPF

I noticed that it is possible to bind variables of the type DateTime to a textbox in WPF. If I enter a wrong value it will not validate and show a red border.
How can I implement my own class, that I can bind to a textbox without having to bind to a property of the class? The Textbox should show a string and the class will validate the input.
Is this possible?
My current solution is this:
In the Model:
public string DefaultLanguageValue
{
get
{
return _defaultLanguageValue;
}
set
{
if (value != this._defaultLanguageValue)
{
ValidateLanguage(value);
this._defaultLanguageValue = value;
NotifyPropertyChanged();
}
}
}
private void ValidateLanguage(string value)
{
string rx = "([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*";
if (!Regex.IsMatch(value, rx))
{
throw new ArgumentException();
}
}
In the XAML:
<TextBox Text="{Binding TreeViewModel.Model.DefaultLanguageValue, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}" BorderThickness="0" MinWidth="100"/>
It would be nice to have a Class that I can just bind like a String, Int or DateTime for examlpe. Any Ideas?
You could bind to the Tag property of the TextBox itself and validate using a ValidationRule:
public class DateValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (!DateTime.TryParse(value as string, out DateTime _))
return new ValidationResult(false, "Invalid date...");
return ValidationResult.ValidResult;
}
}
XAML:
<TextBox>
<TextBox.Text>
<Binding Path="Tag" RelativeSource="{RelativeSource Self}"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:DateValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
This doesn't require you to bind to a view model.
I finally tried the solution suggested by mm8.
The only issue I have now is that if one enters an invalid value into the textbox, it will not update the textbox when I programmatically change the value of the source after clicking a button.
I tried Validation after update, but this allows the user to save invalid values.
<TreeViewItem>
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="chkDefaultLanguage" IsChecked="{Binding TreeViewModel.TreeModel.DefaultLanguage, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="DefaultLanguage: " />
<TextBox BorderThickness="0" MinWidth="100">
<TextBox.Text>
<Binding Path="TreeViewModel.TreeModel.DefaultLanguageValue" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<validationrules:LanguageCodeValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>
class LanguageCodeValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string rx = "([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*";
if (!Regex.IsMatch(value.ToString(), rx))
{
return new ValidationResult(false, "Invalid Language Codee.");
}
return ValidationResult.ValidResult;
}
}

Multibinding with RelayCommand returns unset values

I have a command that binds to a menu item I have, I would like to pass more than one parameter. I have tried using a converter however it seems to return nothing.
My converter
public class AddConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return values.Clone();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
}
My View model containing my command
class myViewModel: ViewModelBase {
private RelayCommand testCommand;
public ICommand TestCommand {
get {
if (testCommand == null) {
testCommand = new RelayCommand((param) => test((object[])param));
}
return testCommand ;
}
}
//Only trying to print out one of the params as a test
public void test(object parameter) {
var values = (object[])parameter;
int num1 = Convert.ToInt32((string)values[0]);
MessageBox.Show(num1.ToString());
}
My binding on my menu item
//Using tags as a test
<ContextMenu>
<MenuItem Name="testing" Header="Move to Position 10" Command="{Binding TestCommand}" Tag="7">
<MenuItem.CommandParameter>
<MultiBinding Converter="{StaticResource AddConverter}">
<Binding ElementName="testing" Path="Tag"/>
<Binding ElementName="testing" Path="Tag"/>
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
</ContextMenu>
After debugging, when I open my window containing the menu item, the converter fires off, the values object is null at that point. Then, when I select my menu item and fire off the command, when I get to my execution, the parameter is null. I don't understand why my converter fires off before I even click the menu item, or why the values are null.
Try to replace the ElementName of the bindings with a RelativeSource. This works for me:
<MenuItem Name="testing" Header="Move to Position 10" Command="{Binding TestCommand}" Tag="7">
<MenuItem.CommandParameter>
<MultiBinding Converter="{StaticResource AddConverter}">
<Binding Path="Tag" RelativeSource="{RelativeSource Self}"/>
<Binding Path="Tag" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
Also note that you should bind to the TestCommand property and not to the testCommand field.

Multibinding not working - StrokeThickness of paths is unchanged

I have an ItemsControl with ItemsSource filled by an ObservableCollection of Paths. The Path class that implements INotifyPropertyChanged has a property named StrokeThickness :
private double _strokeThickness;
public double StrokeThickness
{
get { return _strokeThickness; }
set
{
_strokeThickness = value;
OnPropertyChanged(nameof(StrokeThickness));
}
}
In our ViewModel we have :
public ObservableCollection<Path> PathCollection
{
get { return _pathCollection; }
set
{
_pathCollection = value;
OnPropertyChanged(nameof(PathCollection));
}
}
And this is my View :
<!-- Paths -->
<ItemsControl ItemsSource="{Binding PathCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Stroke="{Binding Stroke}"
Data="{Binding Data}">
<Path.StrokeThickness>
<MultiBinding Converter="{StaticResource
CorrectStrockThiknessConvertor}">
<MultiBinding.Bindings>
<Binding Source="{Binding
StrokeThickness}"></Binding>
<Binding ElementName="RootLayout"
Path="DataContext.ZoomRatio" >
</Binding>
</MultiBinding.Bindings>
</MultiBinding>
</Path.StrokeThickness>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="Canvas"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
I have used a Multibinding Convertor to give me true StrokeThickness based on ZoomRatio. ZoomRatio is being calculated every time map zoomed.
This is my MultiBinding converter :
public class CorrectStrockThiknessConvertor : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values != null & values.Length == 2 && (double)values[0] != 0)
{
return (double)values[1] / (double)values[0];
}
return 1;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
when I trace this Converter its work fine but every time it's entry data for StrokeThikness is same and it means return value doesn't change StrokeThikness of Paths.
Am I doing something to wrong?
The first Binding in the MultiBinding is wrong. It should look like this:
<MultiBinding.Bindings>
<Binding Path="StrokeThickness" />
<Binding ElementName="RootLayout" Path="DataContext.ZoomRatio" />
</MultiBinding.Bindings>
Also check if the property name is correct, as you constantly write StrokeThikness instead of StrokeThickness in your question.
You should also check your converter code. It seems you are dividing ZoomRatio by StrokeThickness, which to my understanding should be the other way round.

MultiValueConverter works with Color property but not with Background property

I have a MultiValueConverter returning one of two SolidColoBrush passed as values[] elements depending on a bool passed as values[] elment as well.
All values[] elements are DependencyProperty.
The problem is that when I bind a Background property to these SolidColorBrush through this MultiValueConverter i get a cast error.
If I bind the SolidColorBrush.Color property to these SolidColorBrush (which seems an error to me unless there is an implicit conversion somewhere) everything works fine at runtime, but the designer does not show the desired Background, it shows always the background associated with the "false" value of my DependencyProperty even if I set the property to true.
The code is this:
XAML - UserControl
<UserControl.Resources>
<local:State2BackgroundConverter x:Key="state2BackgroundConverter"/>
</UserControl.Resources>
<Grid.Background>
<SolidColorBrush>
<SolidColorBrush.Color>
<MultiBinding Converter="{StaticResource state2BackgroundConverter}">
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}" Path="Status"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}" Path="BrushOFF"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}" Path="BrushON"/>
</MultiBinding>
</SolidColorBrush.Color>
</SolidColorBrush>
</Grid.Background> // <--- This works at runtime but not at design time
<Grid.Background>
<MultiBinding Converter="{StaticResource state2BackgroundConverter}">
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}" Path="Status"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}" Path="BrushOFF"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}" Path="BrushON"/>
</MultiBinding>
</Grid.Background> // <--- This generates a cast error
XAML - Window
<LightControls:MyButton Margin="1,0,0,0" Height="80" HorizontalAlignment="Left" Width="80" BrushON="#FF7CFEFF" BrushOFF="#FF0096FF" Status="True"/>
C#
public class Status2ColorConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)values[0] == true)
return ((SolidColorBrush)values[2]);
else
return ((SolidColorBrush)values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return (null);
}
}
private static readonly DependencyProperty StatusProperty =
DependencyProperty.Register("Status",
typeof(bool),
typeof(MyButton),
new PropertyMetadata(tre)
);
public bool Status
{
get { return (bool)this.GetValue(StatusProperty); }
set { this.SetValue(StatusProperty, value); }
}
private static readonly DependencyProperty BrushONProperty = DependencyProperty.Register("BrushON",
typeof(SolidColorBrush),
typeof(MyButton),
new PropertyMetadata(new SolidColorBrush(Colors.Cyan))
);
public SolidColorBrush BrushON
{
get { return (SolidColorBrush)this.GetValue(BrushONProperty); }
set { this.SetValue(BrushONProperty, value); }
}
private static readonly DependencyProperty BrushOFFProperty = DependencyProperty.Register("BrushOFF",
typeof(SolidColorBrush),
typeof(MyButton),
new PropertyMetadata(new SolidColorBrush(Colors.Black))
);
public SolidColorBrush BrushOFF
{
get { return (SolidColorBrush)this.GetValue(BrushOFFProperty); }
set { this.SetValue(BrushOFFProperty, value); }
}
So my questions are:
Why I have to bind SolidColorBrush.Color property even if my BrushON/BrushOFF return SolidColorBrush and not SolidColorBrush.Color?
How I can get this to work at design time? (NOTE: all the other Dependency Property in my project, including those having a custom ValueConverter, not MultiValueConverter, work just fine at design time, even other Background properties)
Thanks in advance!
For those who encounter the same problem, I found an answer to point 2 of my question.
Differently from what happends with IValueConverter, IMultiValueConverter can not evaluate a Dependency Property at design time, even if it has a default value.
Here is the link that gave me the right idea:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/9b902711-2c45-49f9-9478-b36f1e842f0b/imultivalueconverter-error-crashing-visual-studio-at-design-time?forum=wpf
In this article the "as" operator is proposed as solution because it sanifies null variables.
The variable that store the Dependency Property value in my code should not be null at design time (the dependency property has a default value in fact when the same dependency property is used as IValueConverter value everything works fine), so the right thing to do is to prevent this kind of designer misbehaviour by checking if values[] is null and assigning a default return value in that case.
As an example (there could be many smarter ways to obtain the same result):
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string s = values[0] as string;
if (s==null)
{
return (SomeDefaultValue);
}
else
{
Actually do something
}
}

WPF - binding to a property in ViewModel and to other control

In my WPF application (using MVVM) I have a CheckBox and a TextBlock. When the CheckBox is checked the value from the TextBlock will be saved. There is a binding from both controls to my ViewModel. Below simplified XAML:
<StackPanel>
<Label>Add to list</Label>
<CheckBox IsChecked="{Binding Path=AddItem}"></CheckBox>
<Label>Gross amount:</Label>
<TextBlock Text="{Binding Path=Amount}"></TextBlock>
</StackPanel>
Now I would like to have the CheckBox checked when a user starts typing in the TextBlock. I know binding can do that but I already bind to a property in my ViewModel. How can I bind to a property in ViewModel and to other control?
You should use the multibinding. Something like this:
<CheckBox Content="CheckBox" HorizontalAlignment="Left" Margin="191,82,0,0" VerticalAlignment="Top">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource checkConverter}">
<Binding Path="IsChecked"/>
<Binding Path="UserStartedTyping"/>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
checkConverter is a MultiValueConverter that you need in order to decide what to do with the values you are binding with (such as &&, || etc.).
public class CheckConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return (bool)((bool)values[0] || (bool)values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
object[] splitValues = { value, false };
return splitValues;
}
}
UserStartedTyping is a property in the ViewModel that would be set to true when KeyDown event is fired.
Hope it helps.
You can try setting the AddItem to true when the user starts to change the amount value:
private string _amt;
public string Amount
{
get{return _amt;}
set
{
_amt = value;
if(AddItem == false)
AddItem = true;
//PropertyChanges here
}
}

Categories

Resources