WPF passing several parameters including object - c#

I have simple button which do specific action like edit element. And I would like to pass a few parameters including object specifically SelectedMatch from datagrid and SelectedDate from callendar. Now i have something like this :
<DatePicker x:Name="dpEditDateMatch" SelectedDateFormat="Long" SelectedDate="{Binding MyDateTimeProperty2, Mode=TwoWay}" ></DatePicker>
<Button Content="Edit match" Command="{Binding EditMatchCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource Converter}">
<Binding >??</Binding>
<Binding Path="SelectedDate" ElementName="dpEditDateMatch"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
<ScrollViewer>
<DataGrid IsReadOnly="True" ItemsSource="{Binding match}" SelectedItem="{Binding SelectedMatch}"/>
</ScrollViewer>
In .cs file i have definition of SelectedMatch like this:
object _SelectedMatch;
public object SelectedMatch
{
get
{
return _SelectedMatch;
}
set
{
if (_SelectedMatch != value)
{
_SelectedMatch = value;
RaisePropertyChanged("SelectedMatch");
}
}
}
How can i do that ? I mean, handle this from xaml.

I just faced the problem you said yesterday.
And I solved it now.
Here is the XAML:
<Page.Resources>
<local:MultiIcommandParameterConverter x:Key="MultiIcommandParameterConverter"></local:MultiIcommandParameterConverter>
</Page.Resources>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding ButtonClick}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource MultiIcommandParameterConverter}">
<Binding Path="DetailIndex"/>
<Binding Path="DetailContent"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And here is code behind of INotifyPropertyChanged:
public class DetailButtonClass: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChange(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
string _DetailContent;
public string DetailContent
{
get
{
return _DetailContent;
}
set
{
_DetailContent = value;
NotifyPropertyChange("DetailContent");
}
}
string _DetailIndex;
public string DetailIndex
{
get
{
return _DetailIndex;
}
set
{
_DetailIndex = value;
NotifyPropertyChange("DetailIndex");
}
}
CommandClass _ButtonClick;
public override CommandClass ButtonClick
{
get
{
return _ButtonClick;
}
set
{
_ButtonClick = value;
NotifyPropertyChange("ButtonClick");
}
}
public class CommandClass : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public delegate void DelegateClickVoid(string index,string content);
public DelegateClickVoid ClickVoid = null;
public void Execute(object parameter)
{
string[] parameterArray = parameter.ToString().Split(",".ToArray());
ClickVoid?.Invoke(parameterArray[0], parameterArray[1]);
}
}
}
And here is code behind about IMultiValueConverter,the most important things:
public class MultiIcommandParameterConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0] + "," + values[1];
}
object[] IMultiValueConverter.ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Totally,convert all the parameters into one parameter by using ',' to split it.While the button clicked,split the parameter by ',' into parameters.

You already have the data in your ViewModel i.e. MyDateTimeProperty2 which is for your date and SelectedMatch for the match. In your MultiBinding you should pass this:
<Button Content="Edit match" Command="{Binding EditMatchCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource Converter}">
<Binding Path="MyDateTimeProperty2" />
<Binding Path="SelectedMatch" />
</MultiBinding>
</Button.CommandParameter>
</Button>
Because Button already has the same DataContext as the DatePicker and DataGrid you don't even need that x:Name="dpEditDateMatch" on the DatePicker to get the data.

Related

How to bind Multiple Buttons in Combobox

I want to bind a list of buttons in combo boxes. Each Combobox will contains buttons of one category and so on. As referred in attached image.
Below is my code:
<ItemsControl x:Name="iNumbersList">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal" MaxWidth="930"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Click="ItemButtonClick"
Tag="{Binding ItemTag}"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Height="100" Width="300">
<TextBlock TextAlignment="Center" Foreground="Red"
HorizontalAlignment="Center" FontWeight="SemiBold"
FontSize="25" TextWrapping="Wrap"
Text="{Binding ItemDisplayMember}"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
public class NumberModel
{
public string ItemDisplayMember { get; set; }
public object ItemTag { get; set; }
public string ItemCategory { get; set; }
}
How can I group by ItemCategory property and bind it on GUI, a ComboBox for each ItemCategory and then multiple buttons in it?
Probably you don't need a ComboBox but Expander because the objective is reachable using it. ComboBox is needed when you have to filter something inside it or use dropdown displayed above Windows content.
I wrote a simple example using MVVM programming pattern. There will be many new classes but most of it you need add to the project only once. Let's go from the scratch!
1) Create class NotifyPropertyChanged to implement INotifyPropertyChanged interface. It needed to make able Binding to update the layout dynamically in Runtime.
NotifyPropertyChanged.cs
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
2) Create MainViewModel class derived from NotifyPropertyChanged. It will be used for Binding target Properties.
MainViewModel.cs
public class MainViewModel : NotifyPropertyChanged
{
public MainViewModel()
{
}
}
3) Attach MainViewModel to MainWindow's DataContext. One of ways - doing it in xaml.
MainWindow.xaml
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
4) Derive the data class NumberModel from NotifyPropertyChanged and add OnPropertyChanged call for each Property. This will give wonderful effect: when you change any Property in runtime, You'll immediately see the changes in UI. MVVM Magic called Binding :)
NumberModel.cs
public class NumberModel : NotifyPropertyChanged
{
private string _itemDisplayMember;
private object _itemTag;
private string _itemCategory;
public string ItemDisplayMember
{
get => _itemDisplayMember;
set
{
_itemDisplayMember = value;
OnPropertyChanged();
}
}
public object ItemTag
{
get => _itemTag;
set
{
_itemTag = value;
OnPropertyChanged();
}
}
public string ItemCategory
{
get => _itemCategory;
set
{
_itemCategory = value;
OnPropertyChanged();
}
}
}
5) When button is clicked I will not handle the Click event but call a Command. For easy use of Commands I suggest this relaying its logic class (grabbed here).
RelayCommand.cs
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
6) All ready to fill the MainViewModel with code. I've added there a command and some items to collection for test.
MainViewModel.cs
public class MainViewModel : NotifyPropertyChanged
{
private ObservableCollection<NumberModel> _itemsList;
private ICommand _myCommand;
public ObservableCollection<NumberModel> ItemsList
{
get => _itemsList;
set
{
_itemsList = value;
OnPropertyChanged();
}
}
public ICommand MyCommand => _myCommand ?? (_myCommand = new RelayCommand(parameter =>
{
if (parameter is NumberModel number)
MessageBox.Show("ItemDisplayMember: " + number.ItemDisplayMember + "\r\nItemTag: " + number.ItemTag.ToString() + "\r\nItemCategory: " + number.ItemCategory);
}));
public MainViewModel()
{
ItemsList = new ObservableCollection<NumberModel>
{
new NumberModel { ItemDisplayMember = "Button1", ItemTag="Tag1", ItemCategory = "Category1" },
new NumberModel { ItemDisplayMember = "Button2", ItemTag="Tag2", ItemCategory = "Category1" },
new NumberModel { ItemDisplayMember = "Button3", ItemTag="Tag3", ItemCategory = "Category1" },
new NumberModel { ItemDisplayMember = "Button4", ItemTag="Tag4", ItemCategory = "Category2" },
new NumberModel { ItemDisplayMember = "Button5", ItemTag="Tag5", ItemCategory = "Category2" },
new NumberModel { ItemDisplayMember = "Button6", ItemTag="Tag6", ItemCategory = "Category2" },
new NumberModel { ItemDisplayMember = "Button7", ItemTag="Tag7", ItemCategory = "Category3" },
new NumberModel { ItemDisplayMember = "Button8", ItemTag="Tag8", ItemCategory = "Category4" },
new NumberModel { ItemDisplayMember = "Button9", ItemTag="Tag9", ItemCategory = "Category4" }
};
}
}
7) The main answer to your main question is: use IValueConverter to filter the list with requred criteria. I wrote 2 converters. First for Categories, second for Buttons.
Converters.cs
public class CategoryConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ObservableCollection<NumberModel> collection)
{
List<NumberModel> result = new List<NumberModel>();
foreach (NumberModel item in collection)
{
if (!result.Any(x => x.ItemCategory == item.ItemCategory))
result.Add(item);
}
return result;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class ItemGroupConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] is ObservableCollection<NumberModel> collection && values[1] is string categoryName)
{
List<NumberModel> result = new List<NumberModel>();
foreach (NumberModel item in collection)
{
if (item.ItemCategory == categoryName)
result.Add(item);
}
return result;
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
8) Now everything is ready to fill the markup. I post full markup here to make everything clear.
Note: I faced Visual Studio 2019 16.5.4 crash while setting a MultiBinding in ItemsSource and applied the workaround.
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
Title="MainWindow" Height="600" Width="1000" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<local:CategoryConverter x:Key="CategoryConverter"/>
<local:ItemGroupConverter x:Key="ItemGroupConverter"/>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding ItemsList, Converter={StaticResource CategoryConverter}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:NumberModel}">
<Expander Header="{Binding ItemCategory}">
<ItemsControl DataContext="{Binding DataContext,RelativeSource={RelativeSource AncestorType=Window}}">
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="ItemsSource">
<Setter.Value>
<MultiBinding Converter="{StaticResource ItemGroupConverter}">
<Binding Path="ItemsList"/>
<Binding Path="Header" RelativeSource="{RelativeSource AncestorType=Expander}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Style>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" VerticalAlignment="Center" Orientation="Horizontal" MaxWidth="930"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:NumberModel}">
<Button Tag="{Binding ItemTag}"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Height="100" Width="300"
Command="{Binding DataContext.MyCommand,RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}">
<TextBlock TextAlignment="Center" Foreground="Red"
HorizontalAlignment="Center" FontWeight="SemiBold"
FontSize="25" TextWrapping="Wrap"
Text="{Binding ItemDisplayMember}"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Job is done with MVVM. Enjoy. :)
P.S. Ah, yes, I forgot to show you the code-behind class. Here it is!
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}

MultiBinding Color Change on Property Change - How to Clear pragmatically?

Edit: I created a sample project displaying what I have done and what doesn't work. https://github.com/jmooney5115/clear-multibinding
I have a WPF application with controls (textbox, datagrid, etc). When a value changes on the control I need to indicate it by changing the background color. After saving changes the background color needs to go back to the unchanged state without reloading the control. This application is not MVVM, don't judge I inherited it.
I have the code working perfectly for changing the color using MultiBinding and a value converter. The problem is I cannot figure out how to reset the background after calling Save() in my code. I have tried doing DataContext = null and then DataContext = this but the control flickers. There has to be a better way.
Q: how can I reset the background to the unchanged state without reloading the control?
MultiBinding XAML - this works by passing a string[] to BackgroundColorConverter. string[0] is the OneTime binding. string1 is the other binding.
<TextBox.Background>
<MultiBinding Converter="{StaticResource BackgroundColorConverter}">
<Binding Path="DeviceObj.Name" />
<Binding Path="DeviceObj.Name" Mode="OneTime" />
</MultiBinding>
</TextBox.Background>
BackgroundColorConverter.cs
/// <summary>
/// https://stackoverflow.com/questions/1224144/change-background-color-for-wpf-textbox-in-changed-state
///
/// Property changed
/// </summary>
public class BackgroundColorConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var colorRed = (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#FFB0E0E6");
var colorWhite = (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("White");
var unchanged = new SolidColorBrush(colorWhite);
var changed = new SolidColorBrush(colorRed);
if (values.Length == 2)
if (values[0].Equals(values[1]))
return unchanged;
else
return changed;
else
return changed;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Updates
Edit: this is the multi binding for a data grid cell. If the multi binding converter returns true, set the background color to LightBlue. If false, the background is the default color.
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<!-- https://stackoverflow.com/questions/5902351/issue-while-mixing-multibinding-converter-and-trigger-in-style -->
<DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource BackgroundColorConverterBool}">
<Binding Path="Name" />
<Binding Path="Name" Mode="OneTime" />
</MultiBinding>
</DataTrigger.Binding>
</DataTrigger>
<Setter Property="Background" Value="LightBlue"></Setter>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
.
.
.
</DataGrid.Columns>
I made this method to reset the binding of objects after saving.
/// <summary>
/// Update the data binding after a save to clear the blue that could be there when
/// a change is detected.
/// </summary>
/// <typeparam name="T">Type to search for</typeparam>
/// <param name="parentDepObj">Parent object we want to reset the binding for their children.</param>
public static void UpdateDataBinding<T>(DependencyObject parentDepObj) where T : DependencyObject
{
if (parentDepObj != null)
{
MultiBindingExpression multiBindingExpression;
foreach (var control in UIHelper.FindVisualChildren<T>(parentDepObj))
{
multiBindingExpression = BindingOperations.GetMultiBindingExpression(control, Control.BackgroundProperty);
if (multiBindingExpression != null)
multiBindingExpression.UpdateTarget();
}
}
}
Final Update
This question answers how to use MultiBinding for my purpose on DataGridCell: Update MultiBinding on DataGridCell
IHMO a MVVM solution (as Rekshino proposed) is for sure better than a not-MVVM one. The view model should take care about tracing modified data.
Anyway since you inherited this application, you have to consider how much time you need for converting the whole code and sometimes it is not possible. So in this case you can force every single multibinding to "refresh" when you save your data.
Let's suppose this is your XAML (with two or more TextBoxes):
<StackPanel>
<TextBox Margin="5" Text="{Binding DeviceObj.Name, Mode=TwoWay}">
<TextBox.Background>
<MultiBinding Converter="{StaticResource BackgroundColorConverter}">
<Binding Path="DeviceObj.Name" />
<Binding Path="DeviceObj.Name" Mode="OneTime" />
</MultiBinding>
</TextBox.Background>
</TextBox>
<TextBox Margin="5" Text="{Binding DeviceObj.Surname, Mode=TwoWay}">
<TextBox.Background>
<MultiBinding Converter="{StaticResource BackgroundColorConverter}">
<Binding Path="DeviceObj.Surname" />
<Binding Path="DeviceObj.Surname" Mode="OneTime" />
</MultiBinding>
</TextBox.Background>
</TextBox>
<Button Content="Save" Click="Button_Click" Margin="5,10,5,10" />
</StackPanel>
When you click the "Save" Button you can force MultiBindings to update their own targets in this way:
private void Button_Click(object sender, RoutedEventArgs e)
{
MultiBindingExpression multiBindingExpression;
foreach (TextBox textBox in FindVisualChildren<TextBox>(this))
{
multiBindingExpression = BindingOperations.GetMultiBindingExpression(textBox, TextBox.BackgroundProperty);
multiBindingExpression.UpdateTarget();
}
}
You can find the FindVisualChildren implementation in this answer. I hope it can help you.
You have to paste a bool Saved property to your DeviceObj and handle it, if Name or something else been changed.
ViewModel:
public class Device : INotifyPropertyChanged
{
public string Name
{
get
{
return _name;
}
set
{
if (value != _name)
{
_name = value;
Saved = false;
NotifyPropertyChanged(nameof(Name));
}
}
}
private string _name;
public bool Saved
{
get
{
return _saved;
}
set
{
if (value != _saved)
{
_saved = value;
NotifyPropertyChanged(nameof(Saved));
}
}
}
private bool _saved = true;
public void Save()
{
//Saving..
Saved = true;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string info)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
}
}
Converter:
public class BoolToSolColBrushConverter : IValueConverter
{
private static SolidColorBrush changedBr = new SolidColorBrush(Colors.Red);
private static SolidColorBrush unchangedBr = new SolidColorBrush(Colors.Green);
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
if ((bool)value)
{
return unchangedBr;
}
}
catch (Exception)
{
}
return changedBr;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
XAML:
<TextBox Text="{Binding Name}" Background="{Binding Saved, Converter={StaticResiurce BoolToSolColBrushConverter}}" />

ContentStringFormat binding doesn't refresh on propertychange

I'm having an issue causing a label to refresh when it's content doesn't change but its format does. The ContentStringFormat property is bound to the viewmodel and the property change is notified, but the label doesn't update, please find bellow a minimal reproduction exemple in code as well as a project ready to compile/run that demonstrates the issue.
Download project : https://www.dropbox.com/s/rjs1lot09uc2lgj/WPFFormatBindingRefresh.zip?dl=0
XAML :
<StackPanel>
<Label Content="{Binding FirstLabelContent}"></Label>
<Label Content="{Binding SecondLabelContent}" ContentStringFormat="{Binding SecondLabelFormatContent}"></Label>
<Button Click="Button_Click">Add "test" to all bound elements</Button>
</StackPanel>
Code behind :
public event PropertyChangedEventHandler PropertyChanged = (a,b)=> { }; // empty handler avoids checking for null when raising
public string FirstLabelContent { get; set; } = "First Label";
public string SecondLabelContent { get; set; } = "Second";
public string SecondLabelFormatContent { get; set; } = "{0} Label";
void PropertyChange(string PropertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
private void Button_Click(object sender, RoutedEventArgs e)
{
FirstLabelContent += " TEST";
SecondLabelFormatContent += " TEST";
PropertyChange("FirstLabelContent"); // First label correctly updates
PropertyChange("SecondLabelFormatContent"); // Second label doesn't update, expected behavior is changing the format string should cause an update
}
The Label doesn't support refreshing the ContentStringFormat through a binding.
You could use a multi converter like this:
public class MultiConverter2 : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string SecondLabelContent = values[0] as string;
string SecondLabelFormatContent = values[1] as string;
return string.Format(SecondLabelFormatContent, SecondLabelContent);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
XAML:
<StackPanel>
<StackPanel.Resources>
<local:MultiConverter2 x:Key="conv" />
</StackPanel.Resources>
<Label Content="{Binding FirstLabelContent}"></Label>
<Label>
<Label.Content>
<MultiBinding Converter="{StaticResource conv}">
<Binding Path="SecondLabelContent" />
<Binding Path="SecondLabelFormatContent" />
</MultiBinding>
</Label.Content>
</Label>
<Button Click="Button_Click">Add "test" to all bound elements</Button>
</StackPanel>

Converter not getting called at runtime

I have user control with dependency property selected items(which is a collection of enum values)
public IEnumerable SelectedItems
{
get { return (IEnumerable)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IEnumerable),
typeof(UserControl1), new FrameworkPropertyMetadata(OnChangeSelectedItems)
{
BindsTwoWayByDefault = true
});
private static void OnChangeSelectedItems(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uc = d as UserControl1;
if (uc != null)
{
var oldObservable = e.OldValue as INotifyCollectionChanged;
var newObservable = e.NewValue as INotifyCollectionChanged;
if (oldObservable != null)
{
oldObservable.CollectionChanged -= uc.SelectedItemsContentChanged;
}
if (newObservable != null)
{
newObservable.CollectionChanged += uc.SelectedItemsContentChanged;
uc.UpdateMultiSelectControl();
}
}
}
private void SelectedItemsContentChanged(object d, NotifyCollectionChangedEventArgs e)
{
UpdateMultiSelectControl();
}
I have binded selectedItems dependency property with a checkbox in my user control using a converter
<ItemsControl ItemsSource="{Binding Items}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<CheckBox x:Name="ChkBox" Margin="13 0 0 10" Content="{Binding}" >
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource CheckBoxConverter}" Mode="TwoWay" >
<Binding ElementName="ChkBox" Path="Content" />
<Binding RelativeSource="{RelativeSource FindAncestor,AncestorType={x:Type UserControl}}" Path="DataContext.SelectedItems" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay"></Binding>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
and converter
public class CheckBoxConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if (values != null && values.Length > 1)
{
var content = values[0];
var selected = values[1] as IList;
if (selected != null && selected.Contains(content))
{
return true;
}
}
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, System.Globalization.CultureInfo culture)
{
return new[] { Binding.DoNothing, Binding.DoNothing };
}
}
My problems is if I add values in Selected items at the time of construction the converter is getting called but if I add values on a button click ,its not getting called.Can anybody tell me the reason why its happening and how i can correct this one.
The Convert method of the converter only gets called when any of the properties that you bind to changes. You are binding to the Content property of the CheckBox and the SelectedItems property of the UserControl. Neither of these change when you add a new item to the collection.
Try to also bind to the Count property of the collection:
<CheckBox x:Name="ChkBox" Margin="13 0 0 10" Content="{Binding}" >
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource CheckBoxConverter}" Mode="TwoWay" >
<Binding ElementName="ChkBox" Path="Content" />
<Binding RelativeSource="{RelativeSource AncestorType={x:Type UserControl}}" Path="SelectedItems"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type UserControl}}" Path="SelectedItems.Count"/>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>

Binding inside ListBox, need to update

I use Listbox in my application.
<ListBox Name="lbMain">
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Converter={StaticResource convCaption},
Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" IsExpanded="True">
<StackPanel>
<TextBlock Text={Binding FirstName, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}>
</TextBlock>
<TextBlock Text={Binding SecondName, Mode=TwoWay, pdateSourceTrigger=PropertyChanged}>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
code behind
public MainWindow()
{
MyModel myModel = new MyModel();
lbMain.ItemSource = myModel;
}
This is model
public class MyModel
{
public string FirstName{get; set; }
public string SecondName{get; set; }
}
This is converter (convCaption)
public object Convert(object value, Type targetType, object parameter ...)
{
MyModel model =(MyModel)value;
return string.Format("This is {0}, {1}", Model.FirstName, ModelSecondName)
}
It all works. But when I change FirstName or SecondName I need to change Header in Expander. If I write {Mode = TwoWay} show error - "need path". I like to write correctly binding for Expander(to headline the updated)?
You need to implement the INotifyPropertyChanged interface on your MyModel class. Please see the INotifyPropertyChanged Interface page on MSDN for help. Also, you don't really need a Converter to join the two names... there's a much simpler way... just override the ToString() method in that class too:
public override string ToString()
{
return string.Format("This is {0}, {1}", FirstName, SecondName);
}
First your MyModel needs to implement INotifyPropertyChanged and raise PropertyChanged event each time FirstName or SecondName has changed, like so:
public class MyModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
if (_firstName == value) return;
_firstName = value;
OnPropertyChanged("FirstName");
}
}
private string _secondName;
public string SecondName
{
get { return _secondName; }
set
{
if (_secondName == value) return;
_secondName = value;
OnPropertyChanged("SecondName");
}
}
}
and then you can use MultiBinding with StringFormat like so:
<Expander>
<Expander.Header>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}This is {0} {1}">
<Binding Path="FirstName"/>
<Binding Path="SecondName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Expander.Header>
</Expander>

Categories