Validate rows in DataGrid - c#

In my application I have a very large DataGrid with several DataGridTemplateColumns. In the resources of the DataGrid there is a style for the DataGridRows. This style looks like:
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsDirty}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsEditing}" Value="True">
<Setter Property="AttachedProperties:DataGridExtensions.FocusOnEditingColumn" Value="{Binding IsEditing}"/>
</DataTrigger>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
In my ViewModel I'm subscribed to the PropertyChanged-Event of the DataObjects which are contained in the ItemsSource of the DataGrid. So if the PropertyChanged-Event is called and the propertyname equals "IsEdit" I'll do some validations for all rows in the DataGrid. Therfor I'm using the following method:
private void CheckConsistence()
{
foreach (Module module in SelectedGroup.Modules)
{
string currentTarget = ResolveTargetPath(module);
foreach (Module toCompare in SelectedGroup.Modules.Except(new[] { module }))
{
string toCompareTarget = ResolveTargetPath(toCompare);
if (string.Compare(currentTarget, toCompareTarget, StringComparison.InvariantCultureIgnoreCase) == 0)
{
module.IsInvalid = true;
ValidationAdvices.Add(new ValidationAdvice("Duplicated path"));
}
}
}
}
If the IsInvalid-Property in the Module-Object is set to true I change the Error-Object. A part of the Module-Object looks like:
public class Module : INotifyPropertyChanged, IDataErrorInfo
{
private bool isEditing;
public bool IsEditing
{
get { return isEditing; }
set
{
isEditing = value;
OnPropertyChanged(() => IsEditing);
}
}
private bool isInvalid;
public bool IsInvalid
{
get { return isInvalid; }
set
{
isInvalid = value;
if (isInvalid)
{
error = "Error";
}
else
{
error = null;
}
OnPropertyChanged(() => IsInvalid);
OnPropertyChanged(() => Error);
}
}
public string this[string columnName]
{
get { return string.Empty; }
}
private string error;
public string Error
{
get { return error; }
}
}
My DataGrid has a RowValidationErrorTemplate which looks like:
<ControlTemplate x:Key="RowErrorTemplate" TargetType="Control" x:Shared="False">
<Grid ClipToBounds="False" Panel.ZIndex="10000">
<AdornedElementPlaceholder Name="adornedElement"/>
<Image HorizontalAlignment="Center" VerticalAlignment="Center" Width="16" Height="16"
Source="pack://application:,,,/MyApp.UI.Resources;component/Graphics/Error_16x16.png"
ToolTipService.IsEnabled="True"
ToolTipService.ShowOnDisabled="True"
ToolTip="{Binding Converter={converters:ModuleValidationErrorConverter}}"/>
</Grid>
</ControlTemplate>
My problem is now if two rows have the same path both rows should be displayed as error-rows. But only one row will get the errortemplate and the red-background defined in the Style for the DataGridRow. The call of ValidationAdvices.Add(new ValidationAdvice("Duplicated path")); is done for each row. What do I have to do so that every row gets the validationerrortemplate?
I also have set the DataGrid.RowValidationRules like:
<DataGrid.RowValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="True" ValidationStep="UpdatedValue"/>
</DataGrid.RowValidationRules>

Related

WPF Button Background Change on Click and Reset to original when another button in same StackPanel Clicked.

I have a StackPanel with a few buttons. I want to change the color of a button when clicked and reset it to the original when another button in StackPanel is clicked. Is it possible with a single style applied on StackPanel or I have to create Style for each button? If yes then how.
Here is the code of Style applied to StackPanel but this changes the color of the button but does not reset it on clicking another button.
<Style TargetType="StackPanel" x:Key="GlobalStackPanelStyle" BasedOn="{StaticResource FlatStackPanel}">
<Style.Resources>
<Style TargetType="Button">
<Setter Property="Button.Background" Value="Blue"/>
<Style.Triggers>
<Trigger Property="IsPressed" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Button.Background).(SolidColorBrush.Color)" To="Green"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</Style.Resources>
</Style>
Sorry for the delayed response. Here are the steps that you can follow to get the required output.
Assuming you are following MVVM design pattern.
Create buttons in .xaml and bind command to each button as shown below,
<Button Height="32" Width="180" Grid.Column="1" Content="Button 1"
Command="{Binding ClickCommand}" CommandParameter="Button 1">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsButton1Active}" Value="True">
<Setter Property="Background" Value="Green" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding IsButton1Active}" Value="False">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Height="32" Width="180" Grid.Column="2" Content="Button 2"
Margin="5,0,0,0"
Command="{Binding ClickCommand}" CommandParameter="Button 2">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsButton2Active}" Value="True">
<Setter Property="Background" Value="Green" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding IsButton2Active}" Value="False">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Note: You can add how many buttons you want to add to above xaml.
Create 2 boolean properties and set the values of those boolean properties from your ClickCommand method.
private bool isButton1Active;
private bool isButton2Active;
public bool IsButton1Active
{
get { return isButton1Active; }
set { isButton1Active = value; OnPropertyChanged(); }
}
public bool IsButton2Active
{
get { return isButton2Active; }
set { isButton2Active = value; OnPropertyChanged(); }
}
Here is code for command - Add it in your ViewModel
private UICommand _clickCommand;
public UICommand ClickCommand
{
get { return _clickCommand; }
}
Write below statement in your view model constructor
public YourViewModelConstructor()
{
_clickCommand = new UICommand(OnClick);
}
Here is the method that is bound to ClickCommand
private void OnClick(object parameter)
{
switch(parameter.ToString())
{
case "Button 1":
IsButton1Active = true;
IsButton2Active = false;
break;
case "Button 2":
IsButton2Active = true;
IsButton1Active = false;
break;
}
}
Here is the code for my UICommand class
public class UICommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<bool> _canExecute;
public UICommand(Action<object> onExecuteMethod, Func<bool> onCanExecuteMethod =
null)
{
_execute = onExecuteMethod;
_canExecute = onCanExecuteMethod;
}
public bool IsCanExecute { get; set; }
#region ICommand Members
public event EventHandler CanExecuteChanged
{
add { if (_canExecute != null) CommandManager.RequerySuggested += value; }
remove { if (_canExecute != null) CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
public bool CanExecute(object parameter)
{
IsCanExecute = (_canExecute == null || _canExecute());
return IsCanExecute;
}
#endregion
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
}
I assume you know on how to setting datacontext to your window.
This examples gives you an idea on how to achieve functionality by creating some properties created in your ViewModel and bind a command in your View to ViewModel Command property and invoke click command by passing Command Parameter.
Still have any doubts after implementing the solution, kindly let us know.

Style Trigger on sub-property of a DependencyProperty

I have a control I want to style depending on the set MessageType of a custom Message type DependencyProperty of that control.
Custom Type:
public class Message : ObservableObject
{
public MessageTypes MessageType
{
get { return _messageType; }
set {
RaisePropertyChanged(() => MessageType);
_messageType = value;
}
}
public string Text { ... }
...
}
Control:
public class MessageControl : Control
{
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register(
"Message",
typeof(Message),
typeof(MessageControl),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public Message Message
{
get
{
return (Message)GetValue(MessageProperty);
}
set
{
SetValue(MessageProperty, value);
}
}
}
Style:
<ControlTemplate x:Key="MessageControlTemplate"
TargetType="controls:MessageControl">
<Border Background="{TemplateBinding Background}">
<TextBlock Text="{Binding Path=Message.Text,
RelativeSource={RelativeSource TemplatedParent}}" />
</Border>
</ControlTemplate>
<Style TargetType="controls:MessageControl">
<Setter Property="Template"
Value="{StaticResource MessageControlTemplate}" />
<Style.Triggers>
<!-- HERE IS THE ISSUE -->
<Trigger Property="Message.MessageType"
Value="{x:Static classes:MessageType.Error}">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
So the problem is that I can't add a Trigger for Message.MessageType (or basically any other sub property).
Is it even possible to accomplish this? Or should I just create two properties in MessageControl for Text and MessageType?
You could do it with a DataTrigger:
<Style TargetType="controls:MessageControl">
...
<Style.Triggers>
<DataTrigger Binding="{Binding Message.MessageType,
RelativeSource={RelativeSource Self}}"
Value="{x:Static classes:MessageType.Error}">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
You should also raise the PropertyChanged event after setting the property's backing field:
public MessageType MessageType
{
get { return _messageType; }
set
{
_messageType = value;
RaisePropertyChanged(() => MessageType);
}
}

WPF ListView Header Checkbox and MVVM Command

I have a listview in my WPF application and the first column is a Checkbox. This checkbox is bound to the IsSelected property of my model and the event propogation happens correctly.
I also have a Checkbox in the same column's header and want to implement a 'Select All' feature where it checks all the listview items.
I'm using pattern MVVM.
The Event doesn't fire!
Can someone explain what I am doing wrong here..
The relevant code portions are mentioned below..
XAML:
<ListView Grid.Row="0"
ItemsSource="{Binding Path=WorkOrders}"
Margin="5,10,5,5"
Name="WorkOrders"
SelectionMode="Multiple"
FontSize="13"
Background="AliceBlue"
BorderBrush="AliceBlue">
<!--Style of items-->
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<!--Properties-->
<Setter Property="Control.HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Control.VerticalContentAlignment" Value="Center" />
<!--Trigger-->
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{x:Null}" />
<Setter Property="BorderBrush" Value="{x:Null}" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView >
<GridViewColumn CellTemplate="{StaticResource CheckBoxDataTemplate}" Width="80" >
<GridViewColumn.HeaderTemplate>
<DataTemplate>
<CheckBox Command="{Binding Path=SelectAllCommand}" />
</DataTemplate>
</GridViewColumn.HeaderTemplate>
</GridViewColumn>
<GridViewColumn Header="WorkOrder" CellTemplate="{StaticResource DetailIdenTemplate}" Width="300"/>
</GridView>
</ListView.View>
</ListView>
Model:
public class WorkOrder
{
public int CD_WORK_ORDER { get; set; }
public string ID_WORK_ORDER { get; set; }
public bool IsSelected { get; set; }
}
ViewModel:
public class LockWorkOrderSelectionViewModel : ViewModelBase
{
RelayCommand _selectAllCommand;
public ICommand SelectAllCommand
{
get
{
if (_selectAllCommand == null)
{
_selectAllCommand = new RelayCommand(
param => SelectAllElement(),
param => CanSelectAll);
}
//RaiseEvent(new RoutedEventArgs(SearchEvent));
return _selectAllCommand;
}
}
private bool _selectedAllElement;
public bool SelectAllElement()
{
foreach (var item in WorkOrders)
{
item.IsSelected = true;
}
return true;
}
public bool CanSelectAll
{
get { return true; }
}
public List<string> WorkOrdersList
{
get { return _workOrdersList; }
}
private ObservableCollection<WorkOrder> _workOrders = new ObservableCollection<WorkOrder>();
public ObservableCollection<WorkOrder> WorkOrders
{
get
{
int progr = 1;
foreach (var item in WorkOrdersList)
{
if (_workOrders.FirstOrDefault(i => i.ID_WORK_ORDER == item) == null)
{
_workOrders.Add(new WorkOrder { CD_WORK_ORDER = progr, ID_WORK_ORDER = item, IsSelected = false });
progr++;
}
}
return _workOrders;
}
}
}
<CheckBox IsChecked="{Binding DataContext.SelectAll, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
Works for me.

Using DataTriggers in XAML to force events on CheckBox property changing

I have a CheckBox set up something like this:
<CheckBox Unchecked="checkBox_Unchecked">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="IsChecked" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MyVal}" Value="{x:Null}">
<Setter Property="IsChecked" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
Is there any way to get the Unchecked event to fire when my Setter sets IsChecked = False?
This may not be a procedural approach; But for your statement, I guess I should specify that by "force"... You can make this using this work around;
1) In your ViewModel have a boolean property listening to 'INotifyPropertyChanged'
2) Let the default value of property be true;
3) In your constructor re-assign the property's value to 'false'.
4) This will trigger the unchecked event;
XAML:
<CheckBox IsChecked="{Binding CheckboxChecked}" Unchecked="ToggleButton_OnUnchecked"/>
C#:
public partial class MainWindow : INotifyPropertyChanged
{
private bool m_CheckboxChecked = true;
public bool CheckboxChecked
{
get { return m_CheckboxChecked; }
set { m_CheckboxChecked = value; OnPropertyChanged("CheckboxChecked"); }
}
public MainWindow()
{
DataContext = this;
InitializeComponent();
CheckboxChecked = false;
}
private void ToggleButton_OnUnchecked(object sender, RoutedEventArgs e)
{
Console.WriteLine("Un-Checked");
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
You need a Setter for the Case IsChecked = false too. Also I would recommend to bind to a Command in the ViewModel Try this:
<Style.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter Property="Command" Value="{Binding ElementName=this, Path=DataContext.ItemCheckedCommand}"></Setter>
</Trigger>
<Trigger Property="IsChecked" Value="false">
<Setter Property="Command" Value="{Binding ElementName=this, Path=DataContext.ItemCheckedCommand}"></Setter>
</Trigger>
</Style.Triggers>
And define a command where you have your logic in the VM:
public RelayCommand ItemCheckCommand {get; private set;}

Setting Label.Content to Control on DataTrigger

recently I've tried to make label style which would allow to display image or textblock depending on property being set or not. I've bound proper objects to labels' DataContext and prepared reusable style for these labels. Default content is textblock with Name as its text but if IsIconSet property is true, then content would change into image with corresponding IconPath as source.
Similar approach works perfectly with label's properties like background or cursor but in described scenario it breaks up when IsIconSet has the same value in both instances. Then it displays nothing for first label and correct textblock/image for second label.
I've tried to attach converter to Name and IconPath bindings in style in order to check what value is being passed but it seems that it isn't even invoked on first label.
Has anyone managed to do something similar? Am I missing something fundamental? Or maybe there is another approach for such behaviour?
Any help will be appreciate.
Simplified code:
MainWindow
<StackPanel DataContext="{Binding First}">
<Label Style="{StaticResource LabelStyle}" />
</StackPanel>
<StackPanel DataContext="{Binding Second}">
<Label Style="{StaticResource LabelStyle}" />
</StackPanel>
Style
<Style x:Key="LabelStyle" TargetType="Label">
<Setter Property="Content">
<Setter.Value>
<TextBlock Text="{Binding Name}"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsIconSet}" Value="True">
<Setter Property="Content">
<Setter.Value>
<Image Source="{Binding IconPath}"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
Classes
public class ViewModel : INotifyPropertyChanged
{
private LabelClass _first;
private LabelClass _second;
public LabelClass First
{
get => _first;
set
{
_first = value;
OnPropertyChanged();
}
}
public LabelClass Second
{
get => _second;
set
{
_second = value;
OnPropertyChanged();
}
}
public ViewModel()
{
First = new LabelClass("First", "Resources/first.png");
Second = new LabelClass("Second", "Resources/second.png");
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class LabelClass : INotifyPropertyChanged
{
private string _name;
private string _iconPath;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public string IconPath
{
get => _iconPath;
set
{
_iconPath = value;
OnPropertyChanged();
OnPropertyChanged("IsIconSet");
}
}
public bool IsIconSet => !string.IsNullOrEmpty(IconPath);
public LabelClass(string name, string iconPath = null)
{
Name = name;
IconPath = iconPath;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
LabelStyle can be used by multiple Labels, but TextBlock and Image from Content setter are created just once, there is only one instance for all labels, but it cannot be displayed in multiple places. so it is displayed in only one.
to fix the issue use ContentTemplate, like shown below.
<Setter Property="Content" Value="{Binding}"/> line means that entire DataContext is considered as Label Content. Is is necessary for bindings in ContentTemplate
<Style x:Key="LabelStyle" TargetType="Label">
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsIconSet}" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{Binding IconPath}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
alternatively, convert TextBlock and Image to non-shared resources:
<Image Source="{Binding IconPath}" x:Key="Img" x:Shared="False"/>
<TextBlock Text="{Binding Name}" x:Key="Txt" x:Shared="False"/>
<Style x:Key="LabelStyle" TargetType="Label">
<Setter Property="Content" Value="{StaticResource Txt}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsIconSet}" Value="True">
<Setter Property="Content" Value="{StaticResource Img}"/>
</DataTrigger>
</Style.Triggers>
</Style>

Categories