WPF Binding with property index? - c#

I have a project in which I need to bind the background of a TextBox to a value in an array where the index is a property in the DataContext:
Binding backgroundBinding= new Binding();
backgroundBinding.Path = new PropertyPath($"Elements[{Index}].Value");
I've been creating the binding in the codebehind, but want to find a better and more elegant way to do it. Would I have to create a custom converter, or is there some way to reference the Index property in the XAML?

So you've got two options here. I think you're asking for the first one. I've set up two properties in my viewmodel - one for the array of colours, and then one for the index I want to use. I'm binding to them through a MultiConverter to return the correct colour from the array. This will allow you to update your selected index at runtime, and have the background change to the newly selected colour. If you just want a static index that never changes, you should use implement IValueConverter instead of a IMultiValueConverter, and then pass the index in using the ConverterParameter property.
As a side note, I've chosen to implement the array as type Color. SolidColorBrush objects are expensive and doing it this way will help reduce that cost.
public class ViewModel : INotifyPropertyChanged
{
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private Color[] _backgroundColours = new Color[] { Colors.AliceBlue, Colors.Aqua, Colors.Azure };
public Color[] BackgroundColours
{
get => _backgroundColours;
set
{
_backgroundColours = value;
OnPropertyChanged();
}
}
private int _backgroundIndex = 1;
public int ChosenIndex
{
get => _backgroundIndex;
set
{
_backgroundIndex = value;
OnPropertyChanged();
}
}
}
...
public class BackgroundConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var backgroundColours = values[0] as Color[];
var chosenIndex = (int)values[1];
return new SolidColorBrush(backgroundColours[chosenIndex]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
...
<Grid>
<Grid.DataContext>
<local:ViewModel />
</Grid.DataContext>
<Grid.Resources>
<local:BackgroundConverter x:Key="backgroundConverter"/>
</Grid.Resources>
<TextBox>
<TextBox.Background>
<MultiBinding Converter="{StaticResource backgroundConverter}">
<Binding Path="BackgroundColours" />
<Binding Path="ChosenIndex" />
</MultiBinding>
</TextBox.Background>
</TextBox>
</Grid>

Related

C# WPF Binding Path Data with Converter

i have a problem converting a string to a icon. The icon Geometry is in a ResourceDictionary. The ValueConverter is not called (i tried to debug in the Convert Method of the Converter). Here is my code:
xaml:
<Window.Resources>
<local:StatusToPathDataConverter x:Key="PathConverter"/>
</Window.Resources>
<Grid>
<Path Width="20"
Height="20"
Stretch="Uniform"
Fill="Black"
Data="{Binding Path=Status,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource PathConverter}}"/>
</Grid>
cs:
public partial class MainWindow :Window {
public MainWindow() {
InitializeComponent();
}
public string Status
{
get { return (string)GetValue(StatusProperty); }
set { SetValue(StatusProperty, value); }
}
public static readonly DependencyProperty StatusProperty =
DependencyProperty.Register("Status", typeof(string), typeof(MainWindow));
}
public class StatusToPathDataConverter :IValueConverter {
private static ResourceDictionary iconDictionary;
public ResourceDictionary IconDictionary
{
get
{
if(iconDictionary == null) {
iconDictionary = new ResourceDictionary();
iconDictionary.Source = new Uri("/WPFBindingTest;component/Resources/IconDictionary.xaml", UriKind.RelativeOrAbsolute);
}
return iconDictionary;
}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var status = (string)value;
if(statinStatus == null)
return null;
switch(status.ToLower()) {
case "test":
return IconDictionary["TestIcon"];
// ...
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
You're not binding to anything. You need to tell the Binding to go to the Window to find the Status property. The recommended way to do that is to use RelativeSource, as below:
<Path
Width="20"
Height="20"
Stretch="Uniform"
Fill="Black"
Data="{Binding Path=Status,
RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource PathConverter}}"
/>
As #Clemens notes, UpdateSourceTrigger=PropertyChanged doesn't make any sense on this binding and should not be there. That attribute tells the Binding when it should update the binding's source property. The source property is Window.Status, in this case.
However, the Path.Data property does not update the property it's bound to. A Path displays a Geometry; it doesn't edit a Geometry. UpdateSourceTrigger exists for control properties that update viewmodel properties, like TextBox.Text. That's the most common use for UpdateSourceTrigger=PropertyChanged: By default TextBox.Text updates the source property when the TextBox loses focus, but sometimes you want it to update on each keystroke.
Set the DataContext of the window to itself for the binding to work and the Convert method of the converter to get called:
public MainWindow() {
InitializeComponent();
DataContext = this;
}
If the binding to the source property fails the converter will never be invoked.

How to bind ViewModel Property to the dependency property in the convertor

<Window.Resources>
<local:WeightConverter x:Key="weightConverter" RequiredUnit="{Binding VmProp}" />
<TextBlock Text="{Binding Weight, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource weightConverter}}" />
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
In the MyViewModel I have regular property
private string vmProp;
public string VmProp
{
get
{
return "kg";
}
}
And Convertor class has DependencyProperty is:
public class WeightConverter : DependencyObject, IValueConverter
{
public static readonly DependencyProperty RequiredUnitProperty = DependencyProperty.Register("RequiredUnit", typeof(string), typeof(WeightConverter), null);
public string RequiredUnit
{
get
{
return (string)this.GetValue(RequiredUnitProperty);
}
set
{
this.SetValue(RequiredUnitProperty, value);
}
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double dblValue;
if (double.TryParse(value.ToString(), out dblValue))
{
if (this.RequiredUnit == "kg")
{
return dblValue;
}
else
{
return dblValue * 10;
}
return dblValue;
}
return 0;
}
When I do binding in XAML the code works:
<Window.Resources>
<local:WeightConverter x:Key="weightConverter" RequiredUnit="kg"/>
But when I try to bind it to ViewModelProperty the 'RequiredUnit' object is always null.
How can I bind dependency property to ViewModel property?
The reason its null is because you are trying to bind to the view model property from the resources, but the datacontext is not available to the resource. In your output log, you must be getting a binding expression error. Have a look at the output window.
There are multiple ways to get this working.
One way is to give your window a name like x:Name="MainWindow" and then in your binding do like:
<local:WeightConverter x:Key="weightConverter" RequiredUnit="{Binding DataContext.VmProp, ElementName=MainWindow}" />
Another way would be to do it using Relative Source binding.
Put x:Name="leapold" to your Window/Usercontrol
and make your binding with x:reference
<local:WeightConverter x:Key="weightConverter" RequiredUnit="{Binding DataContext.VmProp, Source={x:Reference leapold}}"/>

My c# ComboBox does not call the getter after selecting an item. Why?

I have an Combobox with SelectedItem. If I select an item my setter does some calculation and perhaps I want to reset the value to the old one. Unfortunatly my view does no refresh.
I have the following ComboBox :
<ComboBox BorderThickness="0" VerticalAlignment="Center" Margin="2,0"
DisplayMemberPath="Name"
ItemsSource="{Binding ItemsVS.View}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" >
</ComboBox>
Here are the properties of my ViewModel:
private CollectionViewSource _itemsVS;
public CollectionViewSource ItemsVS
{
get { return _itemsVS; }
set
{
_itemsVS = value;
if(PropertyChanged!=null)
PropertyChanged(this, new PropertyChangedEventArgs("ItemsVS"));
}
}
private ItemViewModel _selectedItem;
public ItemViewModel SelectedItem
{
get
{
// after setting the old value in the setter the getter is not called
// and the view is not refreshed
return _selectedItem;
}
set
{
var old = _selectedItem;
_selectedItem = value;
// After some logic I want to have the old value!!!!!!!!!!!!!!
_selectedItem = old;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
If you want to make the ComboBox read back the current value after setting the new value, you need to add a "no op" Converter to your Binding that effectively does nothing. It's a useful little trick, as a binding will not normally check to see whether the source value actually applied matches the new value provided by the binding. Adding a converter forces it to check.
public sealed class NoOpConverter : IValueConverter
{
public static readonly NoOpConverter Instance = new NoOpConverter();
public object Convert(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
return value;
}
public object ConvertBack(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
return value;
}
}
<ComboBox SelectedItem="{Binding Path=SelectedItem,
Mode=TwoWay,
Converter={x:Static NoOpConverter.Instance}}"
... />

Binding in WPF DataTrigger value

Well, this may be an easy question but I am not able to find a solution for this.
I have a DataTrigger as
<DataTrigger Binding="{Binding Quantity}" Value="0">
Now I want to bind Value to a variable myVariable. So if the value of myVariable changes, the Value property of the DataTrigger changes too.
I have tried setting Binding but it I guess its not possible to set it. Is there any other method by which I can set this Value dynamically.
EDIT : I tried creating a data trigger of my own. But I am still not able to get things working.
This is the code :
DataTrigger d = new DataTrigger();
d.Binding = new Binding("Quantity");
d.Value = 1;
Setter s = new Setter(BackgroundProperty, Brushes.Red);
d.Setters.Add(s);
Style st = new Style(typeof(DataGridRow));
st.Triggers.Add(d);
this.Resources.Add(this.Resources.Count, st);
I want to use the above code in place of following xaml
<Page.Resources>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Quantity}" Value="1">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Page.Resources>
Thanks.
To my understanding of your problem, you are trying to find a way to set the Value of your DataTrigger according to the value of one of the properties of your view model. So here I have a solution.
Here is the View Model
public class ViewModel : INotifyPropertyChanged
{
private string _text;
private string _candidateValue;
public string Text
{
get
{
return this._text;
}
set
{
this._text = value;
if (null != PropertyChanged)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("Text"));
}
}
}
public string CandidateValue
{
get
{
return this._candidateValue;
}
set
{
this._candidateValue = value;
if (null != PropertyChanged)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("Text"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
And you need a IValueConverter in your binding of DataTrigger
public class ValueConverter : DependencyObject, IValueConverter
{
public static readonly DependencyProperty CandidateValueProperty = DependencyProperty.Register("CandidateValue", typeof(string), typeof(ValueConverter));
public string CandidateValue
{
get { return (string)GetValue(CandidateValueProperty); }
set { SetValue(CandidateValueProperty, value); }
}
public ValueConverter()
: base()
{
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (null != value)
{
if (value.ToString() == this.CandidateValue)
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
And the xaml is quite simple
<TextBox x:Name="TextBox" Text="{Binding Text,UpdateSourceTrigger=PropertyChanged}">
</TextBox>
You need to create your datatrigger in code behind.
public MainWindow()
{
InitializeComponent();
//The view model object
ViewModel vm = new ViewModel();
vm.CandidateValue = "1";
this.DataContext = vm;
//create data trigger object for TextBox
DataTrigger d = new DataTrigger();
//create binding object for data trigger
Binding b = new Binding("Text");
ValueConverter c = new ValueConverter();
b.Converter = c;
//create binding object for ValueConverter.CandidateValueProperty
Binding convertBinding = new Binding("CandidateValue");
convertBinding.Source = vm;
BindingOperations.SetBinding(c, ValueConverter.CandidateValueProperty, convertBinding);
d.Binding = b;
d.Value = true;
Setter s = new Setter(TextBox.ForegroundProperty, Brushes.Red);
d.Setters.Add(s);
Style st = new Style(typeof(TextBox));
st.Triggers.Add(d);
this.TextBox.Style = st;
}
The trick here is that the ValueConverter has a dependency property named CandidateValueProperty. And this property is bind to the CandidateValue of view model. So the foreground of the TextBox will be red when the input value equals to CandidateValue of view model.

Adding a TextBlock before another element in a ListBox

I'm currently learning how to develop and building an app for windows phone 7.
If a certain value is true, I need to add a TextBlock to the ListBox before a TextBlock (say its name is x:Name="dayTxtBx").
I am currently using
dayListBox.Items.Add(dayTxtBx);
to add the text box.
Any help very much appreciated!
Thanks
This is pretty easy to do if you're using a DataTemplate and a ValueConverter and passing the whole object into the ListBox (rather than just a string). Assuming you have some object that looks like:
public class SomeObject: INotifyPropertyChanged
{
private bool mTestValue;
public bool TestValue
{
get {return mTestValue;}
set {mTestValue = value; NotifyPropertyChanged("TestValue");}
}
private string mSomeText;
public string SomeText
{
get {return mSomeText;}
set {mSomeText = value; NotifyPropertyChanged("SomeText");}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if ((name != null) && (PropertyChanged != null))
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
You can make a converter that looks like:
public class BooleanVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && (bool)value)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And add the converter to your XAML like so:
<UserControl x:Class="MyProject.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject">
<UserControl.Resources>
<local:BooleanVisibilityConverter x:Key="BoolVisibilityConverter" />
<UserControl.Resources>
Then you could have the ListBox defined in XAML like so:
<Listbox>
<Listbox.ItemTemplate>
<DataTemplate>
<StackPanel Orentation="Horizontal" >
<TextBlock Text="Only Show If Value is True" Visibility={Binding TestValue, Converter={StaticResource BoolVisibilityConverter}} />
<TextBlock Text="{Binding SomeText}" />
</StackPanel>
</DataTemplate>
</Listbox.ItemTemplate>
</Listbox>
Might seem like a lot, but it's really pretty simple once you get started. A great way to learn more about data binding and converters is at Jesse Liberty's blog ( http://jesseliberty.com/?s=Windows+Phone+From+Scratch ).

Categories

Resources