Multi value converter does not update value - c#

I have MultiValueConverter which show value with precision. But it seems that it does not update UI. How it is possible to solve this problem? I can not use string fromat in xaml because precision can be changed in Update(). Is it only one way to specify precision in Update() function witout converter?
Xaml:
<Window.Resources>
<design:PrecisionConverter x:Key="PrecisionConverter"/>
</Window.Resources>
<Grid>
<ToggleButton Height="30" Width="90" >
<ToggleButton.CommandParameter>
<MultiBinding Converter="{StaticResource PrecisionConverter}">
<Binding Path="Rate"/>
<Binding Path="Precision"/>
</MultiBinding>
</ToggleButton.CommandParameter>
</ToggleButton>
</Grid>
Converter:
class PrecisionConverter:IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int precision = int.Parse(values[1].ToString());
double Value = double.Parse(values[0].ToString());
NumberFormatInfo nfi = new NumberFormatInfo();
nfi.NumberDecimalDigits = precision;
return Value.ToString("N",nfi);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Main:
namespace WpfApplication186
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new Data();
}
}
public class Data:INotifyPropertyChanged
{
private double rate;
public double Rate
{
get
{
return this.rate;
}
set
{
this.rate = value;
this.RaisePropertyChanged("Rate");
}
}
private int precision;
public int Precision
{
get
{
return this.precision;
}
set
{
this.precision = value;
this.RaisePropertyChanged("Precision");
}
}
public Data()
{
Action Test = new Action(Update);
IAsyncResult result = Test.BeginInvoke(null,null);
}
public void Update()
{
while(true)
{
System.Threading.Thread.Sleep(1000);
Rate += 0.4324232;
Precision = 2;
}
}
private void RaisePropertyChanged(string info)
{
if (PropertyChanged!=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}

You want to see the converted value?
Replace:
<ToggleButton.CommandParameter>
With
<ToggleButton.Content>

If you can't use a string formatter in XAML, you will have to create a ready-to-go property to bind to in your model.
public string BindMe
{
get { return string.Format("{0} : {1}", Rate, Precision); }
}
And in the setters for Rate and Precision, call
RaisePropertyChanged("BindMe");
Since those will imply an update to BindMe.
And in XAML do a simple bind to BindMe.
<ToggleButton.Content>
<TextBlock Text="{Binding BindMe}" />
</ToggleButton.Content>

Related

IMarkupExtension with bindable properties

I've created an IMarkupExtension for an ImageSource which gets a specified symbol from a specified font and displays it in a specified color with a specified height. Most of the times the icon name is static and I write into the XAML directly. But sometimes there are lists of things that have a property which determines which icon should be used. For this case it is necessary that the icon name is bindable.
Here is (more or less) the current state of my FontImageExtension:
[ContentProperty(nameof(IconName))]
public class FontImageExtension : IMarkupExtension<ImageSource>
{
private readonly IconFontService iconFontService;
[TypeConverter(typeof(FontSizeConverter))]
public double Size { get; set; } = 30d;
public string IconName { get; set; }
public Color Color { get; set; }
public string FontFamily { get; set; }
public FontImageExtension()
{
iconFontService = SomeKindOfContainer.Resolve<IconFontService>();
}
public ImageSource ProvideValue(IServiceProvider serviceProvider)
{
if (string.IsNullOrEmpty(IconName))
return null;
IconFont iconFont = iconFontService.GetIconFont();
if (iconFont == null)
return null;
string glyphCode = iconFont.GetGlyphCode(IconName);
if (string.IsNullOrEmpty(glyphCode))
return null;
FontImageSource fontImageSource = new FontImageSource()
{
FontFamily = iconFont.GetPlatformLocation(),
Glyph = glyphCode,
Color = this.Color,
Size = this.Size,
};
return fontImageSource;
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
return ProvideValue(serviceProvider);
}
}
Most of the time I use it like this in XAML (which already works perfectly):
<Image Source="{m:FontImage SomeIcon, Color=Black, Size=48}"/>
But for dynamic UI (e.g. lists or something) I need it like this:
<CollectionView ItemsSource={Binding SomeCollection}">
<CollectionView.ItemTemplate>
<StackLayout>
<Image Source="{m:FontImage IconName={Binding ItemIcon}, Color=Black, Size=48}"/>
<Label Text="{Binding ItemText}"/>
</StackLayout>
</CollectionView.ItemTemplate>
</CollectionView>
How can I make this work?
It seems you could not use IMarkupExtension with bindable properties .As a 'Binding' can only be set on BindableProperty of a BindableObject.The problem is that MarkupExtension class does not derive from BindableObject, that's why it is not possible to set binding on it's properties.Though you let it implement BindableObject,it still could not work.
A workaround is using Value Converters.
For example:
class ImageSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var p = parameter.ToString().Split('|');
string colorName = p[0];
ColorTypeConverter colorTypeConverter = new ColorTypeConverter();
Color color = (Color)colorTypeConverter.ConvertFromInvariantString(colorName);
double fontSize = double.Parse(p[1]);
//didn't test this here.
IconFontService iconFontService = SomeKindOfContainer.Resolve<IconFontService();
IconFont iconFont = iconFontService.GetIconFont();
if (iconFont == null)
return null;
string glyphCode = iconFont.GetGlyphCode((string)value);
if (string.IsNullOrEmpty(glyphCode))
return null;
FontImageSource fontImageSource = new FontImageSource()
{
FontFamily = iconFont.GetPlatformLocation(),
Glyph = glyphCode,
Color = color,
Size = fontSize,
};
return fontImageSource;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
use in your xaml:
<ContentPage.Resources>
<ResourceDictionary>
<local:ImageSourceConverter x:Key="imageConvert" />
</ResourceDictionary>
</ContentPage.Resources>
<CollectionView ItemsSource={Binding SomeCollection}">
<CollectionView.ItemTemplate>
<StackLayout>
<Image Source="{Binding Name,Converter={StaticResource imageConvert}, ConverterParameter=Color.Black|48}"/>
<Label Text="{Binding ItemText}"/>
</StackLayout>
</CollectionView.ItemTemplate>
</CollectionView>
Also see failed attempt declaring BindableProperty: IMarkupExtension with bindable property does not work and a more ambitious approach to a somewhat different situation - might be relevant: MarkupExtension for binding.
I solved this problem by creating a converter (like #Leo Zhu suggested) but in addition to the IMarkupExtension. So my extension stays as is (with the addition of a constant value that gets used in the converter) and the code for the converter is as follows:
public class FontIconConverter : IValueConverter, IMarkupExtension
{
private IServiceProvider serviceProvider;
public Color Color { get; set; }
[TypeConverter(typeof(FontSizeConverter))]
public double Size { get; set; } = FontIconExtension.DefaultFontSize;
public string FontFamily { get; set; }
public FontIconConverter()
{
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is string iconName))
return null;
var fontIcon = new FontIconExtension()
{
IconName = iconName,
Color = Color,
Size = Size,
FontFamily = FontFamily,
};
return fontIcon.ProvideValue(serviceProvider);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public object ProvideValue(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
return this;
}
}
It then can be used like this:
<Image Source="{Binding IconNameProperty, Converter={c:FontIconConverter Color=Black, Size=48}}"/>
And for static values it stays like this:
<Image Source="{m:FontImage SomeIconsName, Color=Black, Size=48}"/>

UWP: Updating a Background in an ItemTemplate based on a condition

In a UWP project a ListView is bound to a collection player objects. Each player object has a property such as HighScore. The ItemTemplate of the ListView shows the HighScorefor each player. I want to change the Background of the Grid in the ItemTemplate that shows the HighScore when it's HighScore matches the BiggestScore (a property of the Page's DataContext). This represents the largest score across all players. BiggestScore is updated after the HighScore is set.
Any ideas how I can achieve this?
Here is some example code which hopefully illustrates the various pieces.
XAML:
<Grid x:Name="root" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="lvwPlayers" ItemsSource="{Binding Players}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid x:Name="grdHighScore" Background="Yellow">
<TextBlock Text="{Binding HighScore}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
CODE BEHIND:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
var allPlayers = new AllPlayers();
allPlayers.Players.Add(new Player(100));
allPlayers.Players.Add(new Player(112));
allPlayers.Players.Add(new Player(1160));
allPlayers.Players.Add(new Player(122));
this.DataContext = allPlayers;
}
}
PLAYER:
public class Player : INotifyPropertyChanged
{
protected void OnPropertyChanged([CallerMemberName] string caller = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
}
public event PropertyChangedEventHandler PropertyChanged;
public Player( int high)
{
HighScore = high;
}
private int _highScore;
public int HighScore
{
get { return _highScore; }
set
{
_highScore = value;
OnPropertyChanged();
}
}
}
ALLPLAYERS:
public class AllPlayers : INotifyPropertyChanged
{
public ObservableCollection<Player> Players { get; set; }
public AllPlayers()
{
Players = new ObservableCollection<Player>();
}
protected void OnPropertyChanged([CallerMemberName] string caller = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
}
public event PropertyChangedEventHandler PropertyChanged;
public void ChangeScore(int playerIndex, int highScore)
{
Players[playerIndex].HighScore = highScore;
}
private void UpdateBiggestScore()
{
BiggestScore = (from player in Players select player.HighScore).Max();
}
private int _biggestScore;
public int BiggestScore
{
get { return _biggestScore; }
set
{
_biggestScore = value;
OnPropertyChanged();
}
}
}
Create property in your DataContext saying whether HighScore is bigger than BiggestScore, eg. bool IsBiggest
Bind it:
Background={Binding IsBiggest, Converter=HighScoreToColorConverter}
where YourConverter may be something like:
public class HighScoreToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var isBiggest = (bool)value;
var color = isBiggest ? new SolidColorBrush(Colors.Red) : new SolidColorBrush(Colors.Yellow);
return color;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
var color = value as SolidColorBrush;
if (color != null)
{
return color == new SolidColorBrush(Colors.Red);
}
return false;
}
}
You could add the Background Color as a Property to your Player
private SolidColorBrush _backgroundColor = new SolidColorBrush(Colors.Yellow);
public SolidColorBrush BackgroundColor
{
get { return _backgroundColor; }
set
{
_backgroundColor= value;
OnPropertyChanged();
}
}
and bind it to the Background of the Grid
<Grid x:Name="grdHighScore" Background="{Binding BackgroundColor}">
<TextBlock Text="{Binding HighScore}"/>
</Grid>
When you update the Biggest Score, get the player Object with the biggest score and update the Background. Also change the background of all other players back to Yellow.

WPF Converter JSON string to multiple textboxes

I've already made a workaround for this problem because of time constraints at work, although I still want to ask for learning purposes.
So I had this issue where I was making an editor screen for some record data, and in this record was a field called 'Quantity'. However, when designed, it was made a quantity placeholder, but it meant different things. So to explain, it is a SkuReference table, that has a 'Type' that defines if it's a 'Quantity per Pack', 'Roll Length', or 'CBC'. Well, for 'Quantity per Pack' and 'Roll Length', a simple number works, however for the 'CBC' (meaning, Corners/Borders/Centers) the data is stored as a JSON string object:
{ 'Corners': 10, 'Borders': 20, 'Centers': 30 }
Now on the WPF screen, if the data is identified as a 'CBC', I route the data to three textboxes, all bound to the 'Quantity' property of the parent object and using a converter and parameters to identify each one and I put the appropriate value into each textbox. Works fine.
The problem I have is when trying to work the ConvertBack part of the converter. I realized that I do not have reference to the original string property that I can edit and supply the new value to, or access to the other textboxes to just rebuild a new string to return. I was trying to come up with a resolution maybe using MultiBinding in my head, but could not completely come through with an answer.
Is this even possible? BTW I ended up just creating new properties that were split up and when the parent object was set parsed and passed around data. However, for future reference it would seem cleaner to me to just use the original data and a converter without the extra work.
Below is other code for reference:
XAML, UpsertSkuReference.Quantity is the JSON string above
<StackPanel Grid.Column="5">
<TextBlock Text="CBC" />
<StackPanel Orientation="Horizontal">
<TextBox Width="30" Text="{Binding UpsertSkuReference.Quantity, ConverterParameter=co, Converter={StaticResource CBCToIndividualConverter}}" IsEnabled="{Binding CBCIsChecked}" />
<TextBox Width="30" Margin="5,0,0,0" Text="{Binding UpsertSkuReference.Quantity, ConverterParameter=b, Converter={StaticResource CBCToIndividualConverter}}" IsEnabled="{Binding CBCIsChecked}" />
<TextBox Width="30" Margin="5,0,0,0" Text="{Binding UpsertSkuReference.Quantity, ConverterParameter=ce, Converter={StaticResource CBCToIndividualConverter}}" IsEnabled="{Binding CBCIsChecked}" />
</StackPanel>
</StackPanel>
Converter
public class CBCToIndividualConverter : IValueConverter
{
JavaScriptSerializer json = new JavaScriptSerializer();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//--value = CBC JSON object string
//--parameter = [co]: Corners, [b]: Borders, [ce]: Centers
if (value != null)
{
if (parameter == null) { throw new Exception("CBCToIndividualConverter: parameter cannot be null"); }
if (new string[] { "co", "b", "ce" }.Contains(parameter.ToString().ToLower()) == false)
{ throw new Exception("CBCToIndividualConverter: parameter must be 'co' for Corners, 'b' for Borders, or 'ce' for Centers"); }
CornerBorderCenterModel cbc = json.Deserialize<CornerBorderCenterModel>(value.ToString());
switch (parameter.ToString().ToLower())
{
case "co": { return cbc.Corners; }
case "b": { return cbc.Borders; }
case "ce": { return cbc.Centers; }
default: { return null; }
}
}
else { return null; }
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
//--value = number for parameter type
//--parameter = [co]: Corners, [b]: Borders, [ce]: Centers
//--?? Uh Oh
}
return null;
}
}
Converters are not supposed to be used like that. There are cleaner ways. I would suggest you a slight bigger refactoring:
Create 3 properties: Borders, Corners and Centers, on the class that contains the string Quantity (SkuReference?);
When you set any of them, update the Quantity; when you update the Quantity, you try to parse in a CornerBorderCenterModel instance and then update the 3 properties with the values of this instance. All this work is doing implementing the OnPropertyChanged method (see my code later);
In the view, bind every TextBox just with the relative property. This way, you need no converter at all (this works if the external DataContext is the instance of UpsertSkuReference; otherwise, you have to set the DataContext of the StackPanel this way: DataContext="{Binding UpsertSkuReference}", so the bindings of the 3 TextBoxes can find the properties on the object.
The whole round:
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
namespace XXX
{
public class CornerBorderCenterModel
{
public int Corners { get; set; }
public int Borders { get; set; }
public int Centers { get; set; }
}
public class SkuReference : INotifyPropertyChanged
{
static JavaScriptSerializer json = new JavaScriptSerializer();
private static string[] _jsonStringParts =
new[] { nameof(Borders), nameof(Corners), nameof(Centers) };
public SkuReference()
{
PropertyChanged += OnPropertyChanged;
}
public int Corners
{
get { return _Corners; }
set
{
if (_Corners != value)
{
_Corners = value;
RaisePropertyChanged();
}
}
}
private int _Corners;
public int Borders
{
get { return _Borders; }
set
{
if (_Borders != value)
{
_Borders = value;
RaisePropertyChanged();
}
}
}
private int _Borders;
public int Centers
{
get { return _Centers; }
set
{
if (_Centers != value)
{
_Centers = value;
RaisePropertyChanged();
}
}
}
private int _Centers;
private void UpdateCBCFromQuantity()
{
//if Quantity is a CBC and is not null do the following:
var cbc = json.Deserialize<CornerBorderCenterModel>(_Quantity.ToString());
if (cbc != null)
{
Corners = cbc.Corners;
Borders = cbc.Borders;
Centers = cbc.Centers;
}
}
public string Quantity
{
get { return _Quantity; }
set
{
if (_Quantity != value)
{
_Quantity = value;
RaisePropertyChanged();
}
}
}
private string _Quantity;
private void UpdateJsonStringFromCBC()
{
Quantity = string.Format(
"{{ 'Corners': {0}, 'Borders': {1}, 'Centers': {2} }}",
_Corners,
_Borders,
_Centers);
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_jsonStringParts.Contains(e.PropertyName))
UpdateJsonStringFromCBC();
else if (e.PropertyName == nameof(Quantity))
UpdateCBCFromQuantity();
}
}
}
<StackPanel Orientation="Horizontal" DataContext="{Binding UpsertSkuReference}">
<TextBox Text="{Binding Corners}" IsEnabled="{Binding CBCIsChecked}" />
<TextBox Text="{Binding Borders}" IsEnabled="{Binding CBCIsChecked}" />
<TextBox Text="{Binding Centers}" IsEnabled="{Binding CBCIsChecked}" />
</StackPanel>
Note that Quantity, Borders, Corners and Centers must raise notifications when they changed (just as I made, or using more advanced tools like the Reactive library), otherwise the whole round can't work.

IValueConverter.ConvertBack not firing

I want to make my own DateTimePicker (WhenView) and have it two-way bind to a yyyyMMddHHmm string. I'm relatively new to XAML so may be missing something basic!
The WhenView is on the top, which displays Year,Month,Day,Hours, Minutes,Seconds one after another.
A TextBox is on the bottom, which is there just to display the underlying yyyyMMddHHmm string for debug.
Control Model: (When)
public class When : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
int _yea; public int Yea { get { return _yea; } set { _yea = value; PropertyChanged(this, new PropertyChangedEventArgs("Yea")); } }
int _mon; public int Mon { get { return _mon; } set { _mon = value; PropertyChanged(this, new PropertyChangedEventArgs("Mon")); } }
int _day; public int Day { get { return _day; } set { _day = value; PropertyChanged(this, new PropertyChangedEventArgs("Day")); } }
int _hou; public int Hou { get { return _hou; } set { _hou = value; PropertyChanged(this, new PropertyChangedEventArgs("Hou")); } }
int _min; public int Min { get { return _min; } set { _min = value; PropertyChanged(this, new PropertyChangedEventArgs("Min")); } }
int _sec; public int Sec { get { return _sec; } set { _sec = value; PropertyChanged(this, new PropertyChangedEventArgs("Sec")); } }
public When()
: this(DateTime.Now)
{
}
public When(DateTime dt)
{
Yea = dt.Year;
Mon = dt.Month;
Day = dt.Day;
Hou = dt.Hour;
Min = dt.Minute;
Sec = dt.Second;
}
public DateTime ToDateTime()
{
return new DateTime(Yea, Mon, Day, Hou, Min, Sec);
}
}
Control View: (WhenView)
<UserControl x:Class="DateBind.WhenView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel Margin="16">
<TextBox Text="{Binding Yea, Mode=TwoWay}" />
<TextBox Text="{Binding Mon, Mode=TwoWay}" />
<TextBox Text="{Binding Day, Mode=TwoWay}" />
<TextBox Text="{Binding Hou, Mode=TwoWay}" />
<TextBox Text="{Binding Min, Mode=TwoWay}" />
<TextBox Text="{Binding Sec, Mode=TwoWay}" />
</StackPanel>
IValueConverter: (WhenConverter) To facilitate binding to a string:
[ValueConversion(typeof(string), typeof(When))]
public class WhenConverter : IValueConverter
{
const string Format = #"yyyyMMddHHmm";
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is string)
{
DateTime parsedDate = DateTime.ParseExact(value as string, Format, null);
return new When(parsedDate);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is When)
{
if (targetType == typeof(string))
{
return ((When)value).ToDateTime().ToString(Format);
}
}
return null;
}
}
So that's the building blocks done.
MainWindow Code-behind I'm storing the Date string in the MainWindow for brevity, it's in a data class in my real application.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
string _date;
public string Date
{
get { return _date; }
set { _date = value; PropertyChanged(this, new PropertyChangedEventArgs("Date")); }
}
public MainWindow()
{
InitializeComponent();
Date = "1234" + "01" + "02" + "0101";
DataContext = this;
}
}
MainWindow XAML:
(I've added a simple TextBox for testing purposes.)
<Window x:Class="DateBind.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local ="clr-namespace:DateBind">
<StackPanel>
<StackPanel.Resources>
<local:WhenConverter x:Key="WhenConverter"></local:WhenConverter>
</StackPanel.Resources>
<local:WhenView DataContext="{Binding Date, Converter={StaticResource WhenConverter}, Mode=TwoWay}"></local:WhenView>
<TextBox Text="{Binding Date}"></TextBox>
</StackPanel>
What works:
TextBox and WhenControl is initialised with the initial Date successfully
Updating the value in the TextBox updates WhenControl successfully
The problem:
Updating any value in the WhenControl does not update the TextBox (and therefore I expect, the underlying data value)
PropertyChanged is getting fired within the When class, but I don't know what else to do to have it make the TextBox/underlying data update.

Binding Slider value property to one of two class properties - depending on combobox

I have a problem with binding properties.
I have combobox, 4 textboxes, a slider and a class with 4 decimal properties - every property is binded to one textbox. But I have a problem with a slider - depending on combobox I want the slider to be binded to second or fourth property.
Every time I needed binding until now, I could easily do it in XAML, however this time I don't think it's possible.
Ok, so my class [EDITED]:
class Joint : INotifyPropertyChanged
{
private decimal _alfa;
public decimal alfa
{
get { return _alfa; }
set { _alfa = value; OnPropertyChanged(); }
}
private decimal _l;
public decimal l
{
get { return _l; }
set { _l = value; OnPropertyChanged(); }
}
private decimal _lambda;
public decimal lambda
{
get { return _lambda; }
set { _lambda = value; OnPropertyChanged(); }
}
private decimal _theta;
public decimal theta
{
get { return _theta; }
set { _theta = value; OnPropertyChanged(); }
}
private decimal _min;
public decimal min
{
get { return _min; }
set { _min = value; OnPropertyChanged(); }
}
private decimal _max;
public decimal max
{
get { return _max; }
set { _max = value; OnPropertyChanged(); }
}
private TypeOfJoints _type;
public TypeOfJoints type
{
get { return _type; }
set { _type = value; OnPropertyChanged(); }
}
public Joint()
{
alfa = 1.00M;
l = 2.00M;
lambda = 3.00M;
theta = 140.00M;
min = -200.00M;
max = 200.00M;
type = TypeOfJoints.Rotary;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler propertyChangedEvent = PropertyChanged;
if (propertyChangedEvent != null)
propertyChangedEvent(this, new PropertyChangedEventArgs(propertyName));
}
}
My enum[EDITED]:
enum TypeOfJoints
{
Rotary,
Sliding,
}
And the part of my XAML code[EDITED]:
<GroupBox Header="Joint 1">
<StackPanel DataContext="{StaticResource ResourceKey=joint1}">
<ComboBox SelectedItem="{Binding type, Mode=TwoWay}" ItemsSource="{Binding Source={StaticResource JointEnum}}"/>
<TextBox Text="{Binding alfa, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding l, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding lambda, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding theta, UpdateSourceTrigger=PropertyChanged}"/>
<Slider x:Name="slider1" Minimum="{Binding min}" Maximum="{Binding max}" ValueChanged="Slider_ValueChanged">
<Slider.Resources>
<local:SliderValueConverter x:Key="SliderValueConverter" />
</Slider.Resources>
<Slider.Value>
<MultiBinding Converter="{StaticResource SliderValueConverter}">
<Binding Path="type"/>
<Binding Path="lambda"/>
<Binding Path="theta"/>
</MultiBinding>
</Slider.Value>
</Slider>
</StackPanel>
</GroupBox>
And the Validator class:
class SliderValueConverter :IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
TypeOfJoints type = (TypeOfJoints)values.FirstOrDefault();
Console.WriteLine(type);
if (type == TypeOfJoints.Sliding)
return values.ElementAtOrDefault(1);
else if (type == TypeOfJoints.Rotary)
return values.ElementAtOrDefault(2);
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
So now the slider value is binded to property "fourth", but instead, I want this binding to be dependent on ComboBox value - there are two possible: Second and Fourth - the Fourth is the starting value, that's why now I have static binding to property fourth. But I want it to change to value second when the ComboBox value will change.
This is a good usage for MultiBinding. Set it up like this:
<Slider x:Name="slider1" Minimum="{Binding min}" Maximum=" {Binding max}" ValueChanged="Slider_ValueChanged">
<Slider.Resources>
<local:SliderValueConverter x:Key="SliderValueConverter" />
</Slider.Resources>
<Slider.Value>
<MultiBinding Converter="{StaticResource SliderValueConverter}">
<Binding Path="type" />
<Binding Path="second" />
<Binding Path="fourth" />
</MultiBinding>
</Slider.Value>
</Slider>
Note: make sure to use TwoWay binding for ComboBox.SelectedValue so that the view model property will update:
<ComboBox SelectedItem="{Binding type,Mode=TwoWay}" ItemsSource="{Binding Source={StaticResource NumberEnum}}"/>
Lastly, implement SliderValueConverter as an IMultiValueConverter, and return the appropriate value.
public class SliderValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
vartype = values.FirstOrDefault() as TypeOfJoints?;
decimal? val1 = values.ElementAtOrDefault(1) as decimal?,
val2 = values.ElementAtOrDefault(2) as decimal?;
if (type.HasValue && val1.HasValue && val2.HasValue)
{
if (type.Value == TypeOfJoints.Sliding)
return val1.Value;
else if (type.Value == TypeOfJoints.Rotary)
return val2.Value
}
return DependencyProperty.UnsetValue; // no match - return default;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Edit
Another problem: for bindings from a view model source to update, the view model must implement INotifyPropertyChanged, and raise the PropertyChanged event as appropriate. In this case, we would need:
class Joint : INotifyPropertyChanged
{
public decimal first
{
get { return _first; }
set { _first = value; OnPropertyChanged(); }
}
private decimal _first;
// and so forth with the other properties ...
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler propertyChangedEvent = PropertyChanged;
if (propertyChangedEvent != null)
propertyChangedEvent(this, new PropertyChangedEventArgs(propertyName));
}
}

Categories