I created a datagrid where each row represent a command for a machine in our lab. I want to restrain the user's option to select "Relative" in the "Limit Mode" column by implementing validation rules within my code. The user can use a "Relative" limit mode only if the move control associated to this row AND the previous are the same (see picture). Otherwise a message should warn him.
I manage to create a RowValidationRules that works:
XAML:
<DataGrid.RowValidationRules>
<utility:RelativeLimitModeRule ValidationStep="UpdatedValue"/>
</DataGrid.RowValidationRules>
Validation Rule:
class RelativeLimitModeRule : ValidationRule
{
//Makes sure that if "Relative" move position is selected, the previous command had the same MoveCtrl (otherwise relative is bullshit)
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (value is BindingGroup bg && bg.Items.Count > 0)
{
if ((bg.Items[0] as DoliInput).LimMode == "Relative")
{
if (bg.Owner is DataGridRow dgrow)
{
DataGrid dg = GetParent<DataGrid>(dgrow);
int currItemKey = ((bg.Items[0]) as DoliInput).SequenceNumber - 1;
if (currItemKey > 0)
{
if (((((InteractiveGraph.MVVM.ViewModel)dg.DataContext).DoliInputCollection)[currItemKey - 1]).MoveCtrl != (bg.Items[0] as DoliInput).MoveCtrl)
{
return new ValidationResult(false, $"To use the \"Relative\" option, the previous command should have the same Move Ctrl as this one ");
}
}
}
}
}
return ValidationResult.ValidResult;
//return new ValidationResult(true, null);
}
private static T GetParent<T>(DependencyObject d) where T : class
{
while (d != null && !(d is T))
{
d = VisualTreeHelper.GetParent(d);
}
return d as T;
}
}
Where DoliInput
is my model, with four different properties: MoveCtrl
,Speed
,LimitMode
and a last one not visible SequenceNumber
that keeps track of the position of the command within DoliInputCollection
which is an ObservableCollection
that stores/lists all the command to be processed by the machine.
However, when implementing it this way, the validation rule only trigger when the user selects another row AND modifies the "Spe" column.
I modified the XAML so the Validation rule fires as soon as the user selects "Relative" as limit mode:
New Xaml:
<DataGridTemplateColumn Header="Limit Mode">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding LimModeItem}">
<ComboBox.SelectedItem>
<Binding Path="LimMode"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<utility:RelativeLimitModeRule>
</utility:RelativeLimitModeRule>
</Binding.ValidationRules>
</Binding>
</ComboBox.SelectedItem>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding LimModeItem}"
SelectedItem="{Binding LimMode, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
But now the value
parameter in the validation rule returns the select string, and I don't have access to the row index which I need for my comparison between the current row Move Control and the previous one.
Using a the Combobox_DropDownOpened event, I am able to retrieve the row index.
private void ComboBox_DropDownOpened(object sender, EventArgs e)
{
var cb = ((System.Windows.Controls.ComboBox)sender);
DataGridRow dataGridRow = VisualHelper.FindParent<DataGridRow>(cb);
int index = dataGridRow.GetIndex();
}
But I want to keep my CodeBehind clear. So I tried using Interactivity to bind a command to the DropDownEvent, but I don't know how to work on the sender like I would do using the event.
Am I going the right way ? If so, how could I do to retrieve the row number using a command ? Is there a better to setup the validation rule ?
Alright, ValidationStep="UpdatedValue"
not only defines when the Validation rule is fired, but it also has an impact on the value
sent to the validationrule.
By going from
<DataTemplate>
<ComboBox ItemsSource = "{Binding LimModeItem}" >
< ComboBox.SelectedItem >
< Binding Path="LimMode"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<utility:RelativeLimitModeRule/>
</Binding.ValidationRules>
</Binding>
</ComboBox.SelectedItem>
</ComboBox>
</DataTemplate>
To
<DataTemplate>
<ComboBox ItemsSource = "{Binding LimModeItem}" >
< ComboBox.SelectedItem >
< Binding Path="LimMode"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<utility:RelativeLimitModeRule ValidationStep="UpdatedValue"/ >
</Binding.ValidationRules>
</Binding>
</ComboBox.SelectedItem>
</ComboBox>
</DataTemplate>
The type of the value
parameter went from string
to BindingExpression
. From there, I can work my way to the row index.