0

I have defined customcontrol named "DataGridSDU" (Code is simplified). I use it like this:

<UserControl x:Class="MyAssemblyName.MyUserControlV2"
             xmlns:customDataGridSDU="clr-namespace:MyAssemblyName.CustomControls.DataGridSDUControl"
             xmlns:customDataGridColumns="clr-namespace:MyAssemblyName.CustomControls.CustomDataGridStuff.CustomColumns">
    <Grid>
        <customDataGridSDU:DataGridSDU ItemsSource="{Binding CollectionDataFunctionBlock}"  Grid.Row="1" Grid.Column="0"
                                   Grid.ColumnSpan="2" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <customDataGridColumns:DataGridTextColumnWithValidation Binding="{Binding Station, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                                         Header="Station"
                                                         EditingElementStyle="{StaticResource TextBoxEditDataGrid}"
                                                         ElementStyle="{StaticResource TextBoxReadDataGrid}"
                                                         IsReadOnly="True"/>
            </DataGrid.Columns>
        </customDataGridSDU:DataGridSDU>
    </Grid>
</UserControl>

Implementation of DataGridTextColumnWithValidation:

internal class DataGridTextColumnWithValidation : DataGridBoundColumn
{
    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        var textBox = new TextBox();
        var binding = new Binding(((Binding)Binding).Path.Path);
        textBox.SetBinding(TextBox.TextProperty, binding);
        textBox.IsReadOnly = true;


        var style = ElementStyle;
        textBox.Style = style;

        return textBox;
    }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        var textBox = new TextBox();
        var style = EditingElementStyle;
        textBox.Style = style;
        textBox.SetBinding(TextBox.TextProperty, Binding);

        return textBox;
    }
}

Style which is defined in Style.xaml:

<Style TargetType="TextBox" x:Key="TextBoxReadDataGrid" BasedOn="{StaticResource MaterialDesignDataGridTextColumnEditingStyle}"\>

    <!-- Define the default context menu for the style -->
    <Setter Property="ContextMenu">
        <Setter.Value>
            <ContextMenu FontFamily="{Binding PlacementTarget.FontFamily , RelativeSource={RelativeSource Self}}">
                <MenuItem Command="Copy" Icon="{materialDesign:PackIcon Kind=ContentCopy}" />
                <MenuItem Command="Paste" Icon="{materialDesign:PackIcon Kind=ContentPaste}" />
                <Separator/>
                <MenuItem Header="Find and Replace"
                          Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridSDU:DataGridSDU}}, Path=DataContext.OpenFindAndReplaceCommand,UpdateSourceTrigger=PropertyChanged}"
                          Icon="{materialDesign:PackIcon Kind=FindReplace}"
                          InputGestureText="Ctrl+H"/>
                
            </ContextMenu>
        </Setter.Value>
    </Setter>
</Style>

I included the Style.xaml file in App.xaml, and it gets applied to the textboxes within custom columns when the application loads. However, there's an issue with the binding of the Command, specifically for this line:

Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridSDU:DataGridSDU}}, Path=DataContext.OpenFindAndReplaceCommand, UpdateSourceTrigger=PropertyChanged}"

The MenuItem's command is not triggering the command set after the Application first loads. Clicking on this MenuItem doesn't trigger the command. However, if I modify this style during the runtime of the app (changing it and then setting it back to Path=DataContext.OpenFindAndReplaceCommand), it suddenly starts triggering the command in the ViewModel.

I suspect that during the loading process, the Styles.xaml file is applied before the UserControl is fully initialized, causing the binding not to resolve properly. This could be due to the sequence of object initialization and when the binding resolution occurs. I've reached this conclusion because modifying the binding during runtime makes it work, but it doesn't work if I don't modify the binding of the MenuItem in the style during runtime.

Does anyone have insights into why this might be happening in this scenario? And if so, could you provide guidance on how to potentially resolve this issue? Thank you in advance!

I tried to resolve this by updating all bindings of TextBlocks in the attached property for DataGrid when it loads:

public static readonly DependencyProperty UpdateBindingsProperty =
          DependencyProperty.RegisterAttached(
              "UpdateBindings",
              typeof(bool),
              typeof(DataGridExtensions),
              new PropertyMetadata(false, OnUpdateBindingsChanged));

public static bool GetUpdateBindings(DataGrid dataGrid)
{
    return (bool)dataGrid.GetValue(UpdateBindingsProperty);
}

public static void SetUpdateBindings(DataGrid dataGrid, bool value)
{
    dataGrid.SetValue(UpdateBindingsProperty, value);
}

private static void OnUpdateBindingsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if ((bool)e.NewValue) {
        if (d is DataGrid dataGrid) {
            dataGrid.Loaded += dataGrid_Loaded;
        }
    }   
}

private static void dataGrid_Loaded(object sender, RoutedEventArgs e)
{
    DataGrid dataGrid = (DataGrid)sender;

    var rows =  getAllRowsFromDataGrid(dataGrid);

    //Rest of logic to get references to TextBox in each row's cell and then update Command Bindings

    //...
    //...
    
}

private static IEnumerable<DataGridRow> getAllRowsFromDataGrid(DataGrid dataGrid)
{
    var rows = new List<DataGridRow>();

    for (int i = 0; i < dataGrid.Items.Count; i++) {
        DataGridRow row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromItem(i);
        if (row == null) {
            dataGrid.UpdateLayout();
            dataGrid.ScrollIntoView(dataGrid.Items[i]);
            row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(i);
        }
        rows.Add(row);
    }

    return rows;
}

The problem with this is that the getAllRowsFromDataGrid() method takes an enormous time to execute, probably because of invoking too many times ScrollIntoView, I must invoke ScrollIntoView because of virtualization of the data grid.

Reference to that problem

Borisonekenobi
  • 469
  • 4
  • 15
BlastStar
  • 1
  • 2
  • UpdateSourceTrigger=PropertyChanged looks useless – Andy Aug 16 '23 at 18:35
  • Your problem is that contextmenu will not be in the visual tree of the datagrid so your relativesource binding will not work. – Andy Aug 16 '23 at 18:39

0 Answers0