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>
Related
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}}" />
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.
In a view in my application I have the following definition for a DataGridTemplateColumn:
<DataGridTemplateColumn Header="Date and Time" Width="Auto" SortMemberPath="ModuleInfos.FileCreationDateTime">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Width="16" Height="16" ToolTip="Original date changed!"
Source="pack://application:,,,/UI.Resources;component/Graphics/InformationImage.png">
<Image.Visibility>
<MultiBinding Converter="{converters:MultiDateTimeConverter}">
<Binding Path="ModuleInfos.FileCreationDateTime" UpdateSourceTrigger="PropertyChanged"/>
<Binding Path="PartListInfos.ModuleDateTime" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</Image.Visibility>
</Image>
<TextBlock Grid.Column="1" Style="{StaticResource DataGridTextBlockStyle}">
<TextBlock.Text>
<MultiBinding Converter="{converters:MultiDateTimeConverter}">
<Binding Path="ModuleInfos.FileCreationDateTime" UpdateSourceTrigger="PropertyChanged"/>
<Binding Path="PartListInfos.ModuleDateTime" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The MultiDateTimeConverter is:
internal class MultiDateTimeConverter : MarkupExtension, IMultiValueConverter
{
private static MultiDateTimeConverter converter;
private const string dateFormatString = "dd.MM.yyyy - HH:mm:ss";
public MultiDateTimeConverter()
{
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values != null && values.Length == 2 && values[0] is DateTime)
{
if (values[1] is DateTime)
{
if(targetType == typeof(string))
return ((DateTime) values[1]).ToString(dateFormatString);
if (targetType == typeof (Visibility))
return Visibility.Visible;
}
if(targetType == typeof(string))
return ((DateTime)values[0]).ToString(dateFormatString);
if (targetType == typeof (Visibility))
return Visibility.Collapsed;
}
return values;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return converter ?? (converter = new MultiDateTimeConverter());
}
}
My problem is the SortMemberPath of the DataGridTemplateColumn. In the past the content of the column was just bound to the property ModuleInfos.FileCreationDateTime. But now the content depends on the two properties ModuleInfos.FileCreationDateTime and PartListInfos.ModuleDateTime.
What do I have to do to enable the sorting to the correct values?
Add sort-descriptions for as many properties as you want to DataGrid's ItemsSource. Below code sorts by two properties if you click on FileName column header. Use e.Handled = true to stop default sorting behavior.
Conceptually do this :
private void Dgrd_Sorting(object sender, DataGridSortingEventArgs e)
{
DataGridColumn col = e.Column;
if (col.Header.ToString() == "FileName")
{
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(Dgrd.ItemsSource);
view.SortDescriptions.Add(new System.ComponentModel.SortDescription("FileName", System.ComponentModel.ListSortDirection.Ascending));
view.SortDescriptions.Add(new System.ComponentModel.SortDescription("CreatedBy", System.ComponentModel.ListSortDirection.Ascending));
view.Refresh();
e.Handled = true;
}
else
e.Handled = true;
}
I want to bind dictionary to CommandParameter of button.
This my xaml code:
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource MyMultiValueConverter}">
<Binding>
<Binding.Source>
<system:String>SomeString</system:String>
</Binding.Source>
</Binding>
<Binding>
<Binding.Source>
<models:StringObjectPair Key="UserId" Value="{Binding User.Id, UpdateSourceTrigger=PropertyChanged}" />
</Binding.Source>
</Binding>
<Binding>
<Binding.Source>
<models:StringObjectPair Key="UserName" Value="{Binding User.Name, UpdateSourceTrigger=PropertyChanged}" />
</Binding.Source>
</Binding>
</MultiBinding>
</Button.CommandParameter>
StringObjectPair class:
public class StringObjectPair : FrameworkElement
{
public string Key { get; set; }
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(StringObjectPair), new PropertyMetadata(defaultValue: null));
public object Value
{
get { return GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
}
In the MyMultiValueConverter into the values[1].Value and values[2].Value properties I see nulls, but User.Id and User.Name not equals null.
In the output window no errors.
How can I bind this?
First of all, for this kind of scenarios, it is much easier to use move the logic from converter to viewmodel. All inputs for the command should be accessible from viewmodel. Then you don't need command parameter at all:
public class MainWindowViewModel
{
private User _user;
public MainWindowViewModel()
{
MyCommand = new DelegateCommand(() =>
{
//Here you can access to User.Id, User.Name or anything from here
});
}
public DelegateCommand MyCommand { get; private set; }
public User User
{
get { return _user; }
set
{
if (value == _user) return;
_user = value;
OnPropertyChanged();
}
}
}
Second, your original Binding to User.Id didn't worked, because the StringObjectPairs you have created are not in VisualTree and therefore don't have datacontext inherited from parent. However, why not to simplify the multibinding:
<MultiBinding Converter="{StaticResource MyMultiValueConverter}">
<Binding Source="SomeString" />
<Binding Path="User.Id" />
<Binding Path="User.Name" />
</MultiBinding>
var someString = (string)values[0];
var userId = (int)(values[1] ?? 0);
var userName = (string)values[2];
I can imagine even simpler solutution using without multibinding:
<Button Command="..."
CommandParameter="{Binding User, Converter={StaticResource MyConverter},
ConverterParameter=SomeString}" />
public class MyConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var user = (User)value;
var someString = (string)paramenter;
int userId = user.Id;
string userName = user.Name;
return ...
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
EDIT:
if you insist on passing key value pairs to command (although I don't consider it to be a good practice) you can simplify the xaml like this:
<MultiBinding Converter="{StaticResource MyMultiValueConverter}">
<Binding Source="SomeString" ConverterParameter="SomeString" Converter="{StaticResource KeyValueConverter}" />
<Binding Path="User.Id" ConverterParameter="Id" Converter="{StaticResource KeyValueConverter}"/>
<Binding Path="User.Name" ConverterParameter="Name" Converter="{StaticResource KeyValueConverter}"/>
</MultiBinding>
public class KeyValueConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new KeyValuePair<string, object>((string)parameter, value);
}
}
public class DictionaryMultiConverter : IMultiValueConverter
{
public object Convert(object[] keyValues, Type targetType, object parameter, CultureInfo culture)
{
return keyValues.Cast<KeyValuePair<string, object>>()
.ToDictionary(i => i.Key, i => i.Value);
}
}
I found the solution
Changes in StringObjectPair class:
public class StringObjectPair
{
public string Key { get; set; }
public object Value { get; set; }
}
Changes in XAML:
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource MyMultiValueConverter}">
<Binding /><!--This is DataContext-->
<Binding>
<Binding.Source>
<system:String>SomeString</system:String>
</Binding.Source>
</Binding>
<Binding>
<Binding.Source>
<!--Just string in Value-->
<models:StringObjectPair Key="UserId" Value="User.Id" />
</Binding.Source>
</Binding>
<Binding>
<Binding.Source>
<models:StringObjectPair Key="UserName" Value="User.Name" />
</Binding.Source>
</Binding>
</MultiBinding>
</Button.CommandParameter>
And in MyMultiValueConverter I just get the properties on the basis of a Value:
var dataContext = values[0];
var someString = values[1] as string;
var parameters = new Dictionary<string, object>();
for (var i = 2; i < values.Length; i++)
{
var pair = values[i] as StringObjectPair;
if (!string.IsNullOrEmpty(pair?.Key) && !parameters.ContainsKey(pair.Key))
{
var props = (pair.Value as string)?.Split('.') ?? Enumerable.Empty<string>();
var value = props.Aggregate(dataContext, (current, prop) => current?.GetType().GetProperty(prop)?.GetValue(current, null));
parameters.Add(pair.Key, value);
}
}
I have something like this:
<Controls:ToggleRectangleButton.Visibility>
<MultiBinding Converter="{StaticResource MultiButtonCheckedToVisibilityConverter}">
<Binding ElementName="btDayAndNightsLinesTickets" Path="IsButtonChecked" />
<Binding ElementName="btSchoolSemester" Path="IsButtonChecked" />
</MultiBinding>
</Controls:ToggleRectangleButton.Visibility>
MultiButtonCheckedToButtonEnabledConverter's convert method
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool visible = false;
foreach (object value in values)
{
if (value is bool)
{
if ((bool)value == true) visible = true;
}
}
if (visible)
{
return System.Windows.Visibility.Visible;
}
else
{
return System.Windows.Visibility.Hidden;
}
}
So it mean that if at least one of buttons passed as parameters has IsButtonChecked property set to true -> show control. Otherwise hide it.
I want to add some functionality, that is condition:
if ( otherButton.IsChecked ) return System.Windows.Visibility.Hidden;
So if otherButton is checked hide control (independently of the other conditions). I want to be able to set more "otherButtons" than 1 (if at least one of "otherButtons" is checked -> Hide).
Try this:
public class MultiButtonCheckedToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool visible = false;
int trueCount = (int)parameter;
for (int i = 0; i < trueCount; i++)
{
if ((bool)values[i])
{
visible = true;
break;
}
}
if (visible)
{
for (int i = trueCount; i < values.Length; i++)
{
if (!(bool)values[i])
{
visible = false;
break;
}
}
}
if (visible)
{
return System.Windows.Visibility.Visible;
}
else
{
return System.Windows.Visibility.Hidden;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<Button Content="Test">
<Button.Visibility>
<MultiBinding Converter="{StaticResource MultiButtonCheckedToVisibilityConverter}">
<MultiBinding.ConverterParameter>
<sys:Int32>2</sys:Int32>
</MultiBinding.ConverterParameter>
<Binding ElementName="btDayAndNightsLinesTickets" Path="IsChecked" />
<Binding ElementName="btSchoolSemester" Path="IsChecked" />
<Binding ElementName="btOther1" Path="IsChecked" />
<Binding ElementName="btOther2" Path="IsChecked" />
</MultiBinding>
</Button.Visibility>
</Button>
<ToggleButton Name="btDayAndNightsLinesTickets">btDayAndNightsLinesTickets</ToggleButton>
<ToggleButton Name="btSchoolSemester">btSchoolSemester</ToggleButton>
<ToggleButton Name="btOther1">btOther1</ToggleButton>
<ToggleButton Name="btOther2">btOther2</ToggleButton>
The idea is to tell to converter how many buttons shows the control. If this count is not a constant you can refactor converter to receive count as a first binding.
the order of the binding will be kept in the converter code.
you can check the object[] values using an Index and implement your logic according to it.
for example :
if((values[0] is bool) && ((bool)values[0]))
{
//DoSomething
}