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.
Related
I have a listview with values that are being updated constantly from a different thread.
I want to change the color of the background according to the value of the item.
After reading a lot I came to the following conclusions:
The correct way to set background color for list view item is via style selector.
Style selector is called only once in the initialization of the list.
How can I achieve this simple behavior?
xaml:
<Page
x:Class="MyProject.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyProject"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<ListView ItemsSource="{x:Bind ViewModel.DataRef.Values, Mode=OneWay}" HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:ValWrapper">
<TextBlock Text="{x:Bind Val, Mode=OneWay}"/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyleSelector>
<local:CustomItemContainerStyleSelector>
<local:CustomItemContainerStyleSelector.Bad>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="Red"/>
</Style>
</local:CustomItemContainerStyleSelector.Bad>
<local:CustomItemContainerStyleSelector.Good>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="Green"/>
</Style>
</local:CustomItemContainerStyleSelector.CloseToBad>
</local:CustomItemContainerStyleSelector>
</ListView.ItemContainerStyleSelector>
</ListView>
</Grid>
</Page>
cs:
public sealed partial class MainPage : Page
{
public ViewModel ViewModel { get; set; }
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new ViewModel();
}
}
public class CustomItemContainerStyleSelector : StyleSelector
{
public Style Bad { get; set; }
public Style Good { get; set; }
protected override Style SelectStyleCore(object item, DependencyObject container)
{
double threshold = 1;
ValWrapper v = (ValWrapper)item;
if (v.Val <= threshold)
{
return Bad;
}
else {
return Good;
}
}
}
Whenever the data changes, "NotifyPropertyChanged" is called (implements INotifyPropertyChanged).
Please check the following steps:
Set a temporary variable _tempValue to record previous number.
Bind the Background property to IsUpdate, the initial value is all false.
If the number changes, please set IsUpdate to true, then the Background of ListViewItem turns red.
XAML:
<Page
x:Class="Permisson.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Permisson"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<local:ColorConverter x:Key="ColorConverter"/>
</Page.Resources>
<Grid>
<StackPanel>
<ListView ItemsSource="{x:Bind ViewModel.DataRef, Mode=OneWay}" HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate >
<DataTemplate x:DataType="local:ValWrapper">
<Grid Background="{Binding IsUpdate, Converter={StaticResource ColorConverter},Mode=OneWay}">
<TextBlock Text="{Binding Val, Mode=OneWay}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="ChangeNum" Click="Button_Click"/>
<Button Content="ChangeNum2" Click="Button_Click_1"/>
</StackPanel>
</Grid>
</Page>
Code behind:
namespace Permisson
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public ViewModel ViewModel { get; set; }
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new ViewModel();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var v = ViewModel.DataRef[0];
v.Val = 9;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
var v = ViewModel.DataRef[1];
v.Val = 10;
}
}
public class ViewModel
{
private ObservableCollection<ValWrapper> dataRef = new ObservableCollection<ValWrapper>()
{
new ValWrapper {Val=22,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false },
new ValWrapper {Val=25,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false},
new ValWrapper {Val=35,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false},
new ValWrapper {Val=45,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false },
new ValWrapper {Val=55,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false},
new ValWrapper {Val=65,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false }
};
public ObservableCollection<ValWrapper> DataRef { get { return dataRef; } }
}
public class ColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var color = new SolidColorBrush();
if ((bool)value)
{
color.Color = Colors.Red;
}
else
{
color.Color = Colors.Green;
}
return color;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
public class ValWrapper : INotifyPropertyChanged
{
private int val;
private SolidColorBrush brush;
public SolidColorBrush Brush
{
get { return brush; }
set
{
brush = value;
RaisePropertyChanged();
}
}
private int _tempValue;
public int Val
{
get { return val; }
set
{
if(_tempValue != value && _tempValue != 0)
{
IsUpdate = true;
}
val = value;
RaisePropertyChanged();
_tempValue = val;
}
}
private bool _isUpdate;
public bool IsUpdate
{
set
{
_isUpdate = value;
RaisePropertyChanged();
}
get
{
return _isUpdate;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string propertyname = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyname));
}
}
}
}
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.
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>
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));
}
}
I have a particular scenarios. My application looks like this.
In the left side there are some User list Which is a ListBox and at the right side few fields which are data binding to left side. How it works is, if you select "User 1" in the right side user 1 related information will appear and you can modify the information and its is data binding with "UpdateSourceTrigger=PropertyChanged" so it immediately reflects at the left side too. Same case for other users.
Now the problem is if I select multiple users and edit a field say Field 3 which is Editable a textBox. Now If I select user 1 and edit this textbox it reflects in the user 1 "Note: ... " and if I select user 2 and edit the Field 3 it updates the User 2 "Note: ... " but in case of multi selection How do I achieve it? Suppose I want to select user 1 and User 2 both and Edit the Note field It should update both the note fields of user 1 and user 2 and Data binding should also work I mean it should immediately the text i am entering into the textbox. Any ideas how can I achieve this?
Currently in my viewModel
Model
public String Note
{
get
{
return (String)GetValue(NoteProperty);
}
set { SetValue(NoteProperty, value); }
}
View
and in XAML the User ListBox Items template is defined like this
<TextBlock Text="{Binding Note, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
and in the XAML the rightside textbox (field 3) is data bound in the same manner
<TextBox Text="{Binding Note, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
How do I achieve multiple users data binding?
Please help and give me some ideas.
EDIT:
Converter:
public class MultiBindingConverter : IValueConverter
{
ObservableCollection<Info> mycollection;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var coll = (ObservableCollection<Info>)value;
mycollection = coll;
if (coll.Count == 1)
{
if (parameter.ToString() == "FNote")
return coll[0];
}
else if (coll.Count > 1)
{
// string name = coll[0].FirstName;
if (parameter.ToString() == "FNote")
{
string name = coll[0].Note;
foreach (var c in coll)
{
if (c.Note != name)
return null;
else continue;
}
return name;
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (parameter.ToString() == "FNote")
{
foreach (var c in mycollection)
{
c.Note = value.ToString();
}
return mycollection;
}
return null;
}
}
For me only one TextBox Editable NoteTextBox needs to to be DataBinded with multiple Users.
In my ViewModel
I have written
ViewModel
private Command selectionChangedCommand;
public Command SelectionChangedCommand
{
get
{
if (selectionChangedCommand == null)
{
selectionChangedCommand = new Command(SelectionChanged, true);
}
return selectionChangedCommand;
}
set { selectionChangedCommand = value; }
}
public void SelectionChanged(object value)
{
selectedItem = new ObservableCollection<Info>((value as IEnumerable).OfType<Info>());
}
private ObservableCollection<Info> selectedItem;
public ObservableCollection<Info> SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
PropertyChanged("SelectedItem");
}
}
In the Info class there is one property Note which needs to be binded to the View's two places.
I fully agree with #GazTheDestroyer ... this kind of Data Binding can not be achieved through Data binding alone. What #Kumar has suggested is working as a POC, but when you are in a live project and you play with model, viewModel and view and many UserControl with one view model or one User control with two ViewModels, then the difficulty of achieving this scenario is beyond guessing.
Ok, no more theory. I have achieved this and I am going to share how I did so.
One-to-one DataBinding is perfect and working fine. When you select User 4 This user Note field and Field3 Editable NoteBox are bound to the same Property, so it works perfectly.
In multiple selection say User4 is selected first, then you select User3 and user1, I put a logic in code behind that when multiple items are selected Note text is empty. This is not against
MVVM as updating a view based on some criteria of view is not breaking MVVM pattern. So now when the editable text box is updated with some text user4 properties is updated in viewModel. Now the difficult part is to update the other selected users. Here is the code that will update the selected users and will reflect as I have mentioned Mode="TwoWay", UpdateSourceTriger="PropertyChanged"
if (listUser.SelectedItems.Count > 1)
{
for (int i = 0; i < listUser.SelectedItems.Count; i++)
{
Info info = listUser.SelectedItems[i] as Info;
info.Note = (string)tbNote.Text;
}
}
In this way the value of the Editable note textbox is updated in the properties of all the users Note Property and as the binding is two-way, it will reflect in other users too.
There might be many way to solve it, but I found this way and it's working superbly, so I thought I'd answer my own question.
You cannot achieve this via databinding alone, since there are situations where you need to make logical decisions.
For instance, if user1 and user2 have different notetext, then when both are selected you cannot show both at the same time. Instead I guess you want some method of specifying that you want to "keep original text", or allow user to over type to set both texts to be the same.
Whatever you intend, you need to have separate binding sources in your viewmodel so that you can update them independently and make logical decisions.
I tried something with i know and i got output just as your requirement.Please correct me if i'm wrong.
XAML
<Window x:Class="MVVM_sample_ListBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MVVM_sample_ListBox"
Title="MainWindow" Height="350" Width="525"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<Window.Resources>
<local:Converter x:Key="Converter"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="235*" />
<ColumnDefinition Width="268*" />
</Grid.ColumnDefinitions>
<ListBox x:Name="lb" SelectionMode="Multiple" Grid.Row="0" ItemsSource="{Binding MyCollection}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseUp" >
<i:InvokeCommandAction CommandParameter="{Binding SelectedItems, ElementName=lb}" Command="{Binding SelectionChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text="{Binding SecondName}"/>
<TextBlock Text="{Binding Company}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" >
<TextBox Grid.Column="1" Height="23" HorizontalAlignment="Left" Text="{Binding SelectedItem,ConverterParameter=FName, Converter={StaticResource Converter}}" Name="textBox1" VerticalAlignment="Top" Width="120" />
<TextBox Grid.Column="1" Height="23" HorizontalAlignment="Left" Text="{Binding SelectedItem,ConverterParameter=SName, Converter={StaticResource Converter}}" Name="textBox2" VerticalAlignment="Top" Width="120" />
<TextBox Grid.Column="1" Height="23" HorizontalAlignment="Left" Text="{Binding SelectedItem,ConverterParameter=Comp, Converter={StaticResource Converter}}" Name="textBox3" VerticalAlignment="Top" Width="120" />
</StackPanel>
</Grid>
</Window>
C#
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
Model
public class Model : INotifyPropertyChanged
{
private string fname;
public string FirstName
{
get { return fname; }
set { fname = value;RaisePropertyChanged("FirstName"); }
}
private string sname;
public string SecondName
{
get { return sname; }
set { sname = value; RaisePropertyChanged("SecondName");}
}
private string company;
public string Company
{
get { return company; }
set { company = value;RaisePropertyChanged("Company"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string name)
{
if(PropertyChanged!= null)
{
this.PropertyChanged(this,new PropertyChangedEventArgs(name));
}
}
}
ViewModel
public class ViewModel : INotifyPropertyChanged
{
private MyCommand selectionChangedCommand;
public MyCommand SelectionChangedCommand
{
get
{
if (selectionChangedCommand == null)
{
selectionChangedCommand = new MyCommand(SelectionChanged);
}
return selectionChangedCommand;
}
set { selectionChangedCommand = value; }
}
public void SelectionChanged(object value)
{
SelectedItem = new ObservableCollection<Model>((value as IEnumerable).OfType<Model>());
}
private ObservableCollection<Model> selectedItem;
public ObservableCollection<Model> SelectedItem
{
get { return selectedItem; }
set { selectedItem = value; RaisePropertyChanged("SelectedItem"); }
}
private ObservableCollection<Model> mycoll;
public ObservableCollection<Model> MyCollection
{
get { return mycoll;}
set { mycoll = value;}
}
public ViewModel()
{
SelectedItem = new ObservableCollection<Model>();
SelectedItem.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(SelectedItem_CollectionChanged);
MyCollection = new ObservableCollection<Model>();
MyCollection.Add(new Model { FirstName = "aaaaa", SecondName = "bbbbb", Company = "ccccccc" });
MyCollection.Add(new Model { FirstName = "ddddd", SecondName = "bbbbb", Company = "eeeeeee" });
MyCollection.Add(new Model { FirstName = "fffff", SecondName = "gggggg", Company = "ccccccc" });
}
void SelectedItem_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//this.SelectedItem =new ObservableCollection<Model>((sender as ObservableCollection<Model>).Distinct());
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string name)
{
if(PropertyChanged!= null)
{
this.PropertyChanged(this,new PropertyChangedEventArgs(name));
}
}
}
public class MyCommand : ICommand
{
private Action<object> _execute;
private Predicate<object> _canexecute;
public MyCommand(Action<object> execute, Predicate<object> canexecute)
{
_execute = execute;
_canexecute = canexecute;
}
public MyCommand(Action<object> execute)
: this(execute, null)
{
_execute = execute;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
if (parameter == null)
return true;
if (_canexecute != null)
{
return _canexecute(parameter);
}
else
{
return true;
}
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion
}
Converter
public class Converter : IValueConverter
{
ObservableCollection<Model> mycollection;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var coll = (ObservableCollection<Model>)value;
mycollection = coll;
if (coll.Count == 1)
{
if (parameter.ToString() == "FName")
return coll[0].FirstName;
else if (parameter.ToString() == "SName")
return coll[0].SecondName;
else if (parameter.ToString() == "Comp")
return coll[0].Company;
}
else if(coll.Count >1)
{
// string name = coll[0].FirstName;
if (parameter.ToString() == "FName")
{
string name = coll[0].FirstName;
foreach (var c in coll)
{
if (c.FirstName != name)
return null;
else continue;
}
return name;
}
if (parameter.ToString() == "SName")
{
string name = coll[0].SecondName;
foreach (var c in coll)
{
if (c.SecondName != name)
return null;
else continue;
}
return name;
}
if (parameter.ToString() == "Comp")
{
string name = coll[0].Company;
foreach (var c in coll)
{
if (c.Company != name)
return null;
else continue;
}
return name;
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (parameter.ToString() == "FName")
{
foreach (var c in mycollection)
{
c.FirstName = value.ToString();
}
return mycollection;
}
else
if (parameter.ToString() == "SName")
{
foreach (var c in mycollection)
{
c.SecondName = value.ToString();
}
return mycollection;
}
else
if (parameter.ToString() == "Comp")
{
foreach (var c in mycollection)
{
c.Company = value.ToString();
}
return mycollection;
}
return null;
}
}