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;
}
Related
It's a way to enable and disable a WPF Converter? Either programmatically or directly from WPF binding a checkbox control to it.
I have this Textbox and Checkbox in my application:
When Checkbox is unchecked I can enter any numeric value, but when I Check the checkbox I want to enable this converter:
<TextBox
Grid.Row="1"
Grid.Column="1"
Margin="0,0,10,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
MaxLength="41"
Text="{
Binding Payload,
Mode=TwoWay,
Converter={StaticResource HexStringConverter},
UpdateSourceTrigger=PropertyChanged}"
/>
Also, this is the converter class:
public class HexStringConverter : IValueConverter
{
private string lastValidValue;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string ret = null;
if (value != null && value is string)
{
var valueAsString = (string)value;
var parts = valueAsString.ToCharArray();
var formatted = parts.Select((p, i) => (++i) % 2 == 0 ? String.Concat(p.ToString(), " ") : p.ToString());
ret = String.Join(String.Empty, formatted).Trim();
}
return ret;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
object ret = null;
if (value != null && value is string)
{
var valueAsString = ((string)value).Replace(" ", String.Empty).ToUpper();
ret = lastValidValue = IsHex(valueAsString) ? valueAsString : lastValidValue;
}
return ret;
}
private bool IsHex(string text)
{
var reg = new System.Text.RegularExpressions.Regex(#"^[0-9A-Fa-f\[\]]*$");
return reg.IsMatch(text);
}
}
As usual in WPF, there are many ways to do this.
One way is to use a trigger to change the binding given to Text, something like:
<TextBlock ....>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Payload}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=NameOfCheckBox}" Value="True">
<Setter Property="Text"
Value="{Binding Payload, Converter={StaticResource HexToStringConverter}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Another way is to use an IMultiValueConverter:
public class HexStringConverter : IMultiValueConverter
{
public object Convert (object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length != 2 ||
values[0] is not string str ||
values[1] is not bool isEnabled)
{
return DependencyProperty.UnsetValue;
}
if (isEnabled)
{
// Do the actual conversion
}
else
{
return str;
}
}
}
Then:
<TextBlock ...>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource HexToStringConverter}">
<Binding Path="Payload"/>
<Binding Path="IsChecked" ElementName="NameOfCheckBox"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
I have a set of converters that are used dynamically position squares on a canvas. The squares are stored in an observable collection. CanvasPositionScaleConverter converts a value within a range to a value between 0 and 1. If the value it is converting is outside the specified range, it will throw an Argument exception.
My issue is when I clear my squares collection, whilst the screen becomes empty, "phantom" elements seem to be left behind which the converters continue to act on. Therefore, when the canvas is resized, I still get exceptions being thrown, even if the squares collection has been cleared.
Why are the converters still running on these elements that have been deleted through Squares.Clear()?
Note
Using Squares = new ObservableCollection<Square>() instead of Squares.Clear() still causes this issue.
Update
This issue does not happen if the Sqaures collection is never added too and Resize is pressed. It only happens if elements have been deleted from the collection (and obviously if haven't been removed from the collection but are outside the valid range).
The exception details are
Exception thrown: 'System.ArgumentException' in LateExceptionMcve.dll
An unhandled exception of type 'System.ArgumentException' occurred in LateExceptionMcve.dll
Value cannot be greater than max
MCVE
MainWindow.xaml
<Window x:Class="LateExceptionMcve.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:LateExceptionMcve"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:MainWindow, IsDesignTimeCreatable=True}"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Viewbox Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="0"
Stretch="Uniform">
<Border BorderThickness="1"
BorderBrush="Black">
<Border.Resources>
<local:CanvasScaleConverter x:Key="CanvasScaleConverter" />
<local:CanvasPositionScaleConverter x:Key="CanvasPositionScaleConverter" />
</Border.Resources>
<ItemsControl ItemsSource="{Binding Squares}"
Width="{Binding Size, Converter={StaticResource CanvasScaleConverter}}"
Height="{Binding Size, Converter={StaticResource CanvasScaleConverter}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left">
<Setter.Value>
<MultiBinding Converter="{StaticResource CanvasPositionScaleConverter}">
<Binding Path="X" />
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}"
Path="DataContext.Min" />
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}"
Path="DataContext.Max" />
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}"
Path="DataContext.Size"
Converter="{StaticResource CanvasScaleConverter}" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Canvas.Top">
<Setter.Value>
<MultiBinding Converter="{StaticResource CanvasPositionScaleConverter}">
<Binding Path="Y" />
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}"
Path="DataContext.Min" />
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}"
Path="DataContext.Max" />
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}"
Path="DataContext.Size"
Converter="{StaticResource CanvasScaleConverter}" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Width" Value="50" />
<Setter Property="Height" Value="50" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Background="Red" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</Viewbox>
<Button Grid.Column="0"
Grid.Row="1"
Content="Clear"
Click="Clear" />
<Button Grid.Column="1"
Grid.Row="1"
Content="Resize"
Click="Resize" />
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Data;
namespace LateExceptionMcve
{
public partial class MainWindow : INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
Min = 0;
Max = 10;
Size = Max - Min;
Squares = new ObservableCollection<Square>
{
new Square
{
X = 8,
Y = 7,
},
};
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void Resize(object sender, RoutedEventArgs e)
{
Min = 0;
Max = 5;
Size = Max - Min;
}
private void Clear(object sender, RoutedEventArgs e)
{
Squares.Clear();
}
private ObservableCollection<Square> _squares;
public ObservableCollection<Square> Squares
{
get => _squares;
set
{
_squares = value;
OnPropertyChanged();
}
}
private double _size;
public double Size
{
get => _size;
set
{
_size = value;
OnPropertyChanged();
}
}
private double _min;
public double Min
{
get => _min;
set
{
_min = value;
OnPropertyChanged();
}
}
private double _max;
public double Max
{
get => _max;
set
{
_max = value;
OnPropertyChanged();
}
}
}
public sealed class Square : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private double _x;
public double X
{
get => _x;
set
{
_x = value;
OnPropertyChanged();
}
}
private double _y;
public double Y
{
get => _y;
set
{
_y = value;
OnPropertyChanged();
}
}
}
public sealed class CanvasScaleConverter : IValueConverter
{
private const double Scale = 100;
public object Convert(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
if (value is double valueToScale)
{
return valueToScale * Scale;
}
return value;
}
public object ConvertBack(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
throw new NotSupportedException();
}
}
public sealed class CanvasPositionScaleConverter : IMultiValueConverter
{
public object Convert(
object[] values,
Type targetType,
object parameter,
CultureInfo culture)
{
if (values.Length == 4 &&
values[0] is double value &&
values[1] is double min &&
values[2] is double max &&
values[3] is double canvasWidthHeight)
{
return canvasWidthHeight * RangeToNormalizedValue(min, max, value);
}
return values;
}
private static double RangeToNormalizedValue(
double min,
double max,
double value)
{
if (min > max)
{
throw new ArgumentException("Min cannot be less than max");
}
if (value < min)
{
throw new ArgumentException("Value cannot be less than min");
}
if (value > max)
{
throw new ArgumentException("Value cannot be greater than max");
}
return (value - min) / (max - min);
}
public object[] ConvertBack(
object value,
Type[] targetTypes,
object parameter,
CultureInfo culture)
{
throw new NotSupportedException();
}
}
}
Edit
I have filled a bug report on the WPF GitHub repo, so hopefully that may help find what is going on here.
Information from miloush on the GitHub issue.
The cause of this issue is that even though the Squares collection has been cleared and the objects are no longer visible on the screen, the objects have not been garbage collected.
It is possible to force the garbage collection to happen by modifying the Clear method to something like
private void Clear(object sender, RoutedEventArgs e)
{
Squares.Clear();
Dispatcher.BeginInvoke(new Action(GC.Collect), DispatcherPriority.ContextIdle);
}
And this will (eventually) clear the objects away. However, it is not immediate. Even if just GC.Collect() after Squares.Clear had been called it is not immediate.
This issue could occur at many times in WPF, including in common actions such as virtualisation. Therefore it would be better to update the converter so it can handle invalid inputs gracefully.
public sealed class CanvasPositionScaleConverter : IMultiValueConverter
{
public object Convert(
object[] values,
Type targetType,
object parameter,
CultureInfo culture)
{
if (values.Length != 4 ||
!(values[0] is double value) ||
!(values[1] is double min) ||
!(values[2] is double max) ||
!(values[3] is double canvasWidthHeight))
{
return null;
}
try
{
return canvasWidthHeight * RangeToNormalizedValue(min, max, value);
}
catch (ArgumentException e) when (e.Message == ValueLessThanMin)
{
return 0;
}
catch (ArgumentException e) when (e.Message == ValueGreaterThanMax)
{
return canvasWidthHeight;
}
}
// This is in a utility class normally
private const string ValueLessThanMin = "Value cannot be less than min";
private const string ValueGreaterThanMax = "Value cannot be greater than max";
private static double RangeToNormalizedValue(
double min,
double max,
double value)
{
if (min > max)
{
throw new ArgumentException("Min cannot be less than max");
}
if (value < min)
{
throw new ArgumentException(ValueLessThanMin);
}
if (value > max)
{
throw new ArgumentException(ValueGreaterThanMax);
}
return (value - min) / (max - min);
}
public object[] ConvertBack(
object value,
Type[] targetTypes,
object parameter,
CultureInfo culture)
{
throw new NotSupportedException();
}
}
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>
I'm using a System.Windows.Controls.DataGrid (WPF, .NET 4.0, C#). If a cell's validation fails (HasErrors == TRUE) the OK button should be gray (IsEnabled = FALSE).
The DataGrid validation is performed using a ValidationRule.
I've read several closely related articles here on StackOverflow, but I'm still jammed. I think the problem is that the Validation is on the DataGridRow, but the OK button's IsEnabled binding is looking at the whole grid.
To see the error, add a third row to the grid, and put in an invalid number (e.g. 200).) Or just edit one of the two stock values to be invalid (less than 0, non-integer, or greater than 100).
Here is the xaml:
<Window x:Class="SimpleDataGridValidation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:SimpleDataGridValidation"
Title="MainWindow"
Height="350"
Width="525">
<Window.Resources>
<c:BoolToOppositeBoolConverter x:Key="boolToOppositeBoolConverter" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<DataGrid Name="myDataGrid"
ItemsSource="{Binding}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Time"
x:Name="TimeField">
<DataGridTextColumn.Binding>
<Binding Path="Time"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<c:TimeValidationRule />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
</DataGrid.Columns>
<DataGrid.RowValidationErrorTemplate>
<ControlTemplate>
<Grid Margin="0,-2,0,-2"
ToolTip="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}},
Path=(Validation.Errors)[0].ErrorContent}">
<Ellipse StrokeThickness="0"
Fill="Red"
Width="{TemplateBinding FontSize}"
Height="{TemplateBinding FontSize}" />
<TextBlock Text="!"
FontSize="{TemplateBinding FontSize}"
FontWeight="Bold"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</DataGrid.RowValidationErrorTemplate>
</DataGrid>
<Button Name="btnOk"
Width="40"
Content="_OK"
IsDefault="True"
Grid.Row="1"
Click="btnOk_Click"
IsEnabled="{Binding ElementName=myDataGrid, Path=(Validation.HasError), Converter={StaticResource boolToOppositeBoolConverter}}">
</Button>
</Grid>
</Window>
And here is the C# code-behind:
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace SimpleDataGridValidation
{
public partial class MainWindow : Window
{
public ObservableCollection<CTransition> Transitions;
public MainWindow()
{
InitializeComponent();
Transitions = new ObservableCollection<CTransition>();
Transitions.Add(new CTransition(10, 5));
Transitions.Add(new CTransition(20, 7));
myDataGrid.DataContext = Transitions;
}
private void btnOk_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
}
public class CTransition
{
public int Time { get; set; }
public int Speed { get; set; }
public CTransition()
{ }
public CTransition(int thetime, int thespeed)
{
Time = thetime;
Speed = thespeed;
}
}
public class TimeValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value != null)
{
int proposedValue;
if (!int.TryParse(value.ToString(), out proposedValue))
{
return new ValidationResult(false, "'" + value.ToString() + "' is not a whole number.");
}
if (proposedValue > 100)
{
return new ValidationResult(false, "Maximum time is 100 seconds.");
}
if (proposedValue < 0)
{
return new ValidationResult(false, "Time must be a positive integer.");
}
}
// Everything OK.
return new ValidationResult(true, null);
}
}
[ValueConversion(typeof(Boolean), typeof(Boolean))]
public class BoolToOppositeBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType != typeof(bool) && targetType != typeof(System.Nullable<bool>))
{
throw new InvalidOperationException("The target must be a boolean");
}
if (null == value)
{
return null;
}
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType != typeof(bool) && targetType != typeof(System.Nullable<bool>))
{
throw new InvalidOperationException("The target must be a boolean");
}
if (null == value)
{
return null;
}
return !(bool)value;
}
}
}
I got it to work by adding 3 things:
"NotifyOnValidationError = True" in DataGridTextColumn's Binding
AddHandler(Validation.ErrorEvent, new RoutedEventHandler(OnErrorEvent)); - Added to MainWindow's constructor.
OnErrorEvent function that keeps track of the error counts and sets the OK button's IsEnabled status. - See Disable/enable button with DataGridTextColumn validation and Using WPF Validation rules and disabling a 'Save' button
I am simply trying to bind two controls as command parameters and pass them into my command as an object[].
XAML:
<UserControl.Resources>
<C:MultiValueConverter x:Key="MultiParamConverter"></C:MultiValueConverter>
</UserControl.Resources>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Button Name="Expander" Content="+" Width="25" Margin="4,0,4,0" Command="{Binding ExpanderCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource MultiParamConverter}">
<Binding ElementName="Content"/>
<Binding ElementName="Expander"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
<Label FontWeight="Bold">GENERAL INFORMATION</Label>
</StackPanel>
<StackPanel Name="Content" Orientation="Vertical" Visibility="Collapsed">
<Label>Test</Label>
</StackPanel>
</StackPanel>
Command:
public ICommand ExpanderCommand
{
get
{
return new RelayCommand(delegate(object param)
{
var args = (object[])param;
var content = (UIElement)args[0];
var button = (Button)args[1];
content.Visibility = (content.Visibility == Visibility.Visible) ? Visibility.Collapsed : Visibility.Visible;
button.Content = (content.Visibility == Visibility.Visible) ? "-" : "+";
});
}
}
Converter:
public class MultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return values;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException("No two way conversion, one way binding only.");
}
}
Basically what is happening is that the binding seems to be working fine and the converter is returning an object[] containing the correct values, however when the command executes the param is an object[] containing the same number of elements except they are null.
Can someone please tell me why the values of the object[] parameter are being set to null?
Thanks,
Alex.
This'll do it:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.ToArray();
}
Take a look at this question for the explanation.