WPF datagrid binding errors for cell background color when out of view

Cyril77 1 Reputation point
2021-03-15T18:43:34.46+00:00

Hi all,

I'm fairly new at WPF, but managed to create a datagrid that basically works.. At least visually, everything looks good: I'm able to change the cell's background based on a DisplayColor property in the data source.

However, I first noticed slowdowns in the app, and it turns out that there are binding errors I can't get rid of while keeping the functionality (if I remove the binding to the background color, they go away).

The error in the XAML Binding Failures reads: "Cannot find governing FrameworkElement or FrameworkContentElement for target element." They occur twice:

  • upon initially displaying the grid, and the Datacontext says "null": only 1 error per bound column, so not a big deal
  • when scrolling rows in/out of view: quickly reaching hundreds of errors and causing slowdowns in the application eg when I replace the grid in its ContentControl by another user control.

There also seems to be a link with virtualization: if I set EnableRowVirtualization to false, the errors related to scrolling disappear. However, I still get the errors (and related delay) when I swap the content of the ContentControl this datagrid is located in (through a User Control) with another user control.

I am creating the datagrid's columns in code-behind, because I don't know upfront how many columns are required. Here the code lines that seem to be causing the issue:

                    storeColumns.Add(new DataGridTemplateColumn());

                    // create a cell template
                    Binding textBinding = new Binding($"ScheduleListForTheDay[{i}].DisplayText");
                    textBinding.Mode = BindingMode.OneWay;
                    // create a textblock
                    FrameworkElementFactory textFactory = new FrameworkElementFactory(typeof(TextBlock));
                    textFactory.SetBinding(TextBlock.TextProperty, textBinding);
                    textFactory.SetValue(HorizontalAlignmentProperty, HorizontalAlignment.Center);
                    DataTemplate textTemplate = new DataTemplate(typeof(TextBlock));
                    textTemplate.VisualTree = textFactory;

                    // create a cell editing template
                    Binding comboBinding = new Binding($"ScheduleListForTheDay[{i}].EmployeeID");
                    comboBinding.Mode = BindingMode.TwoWay;
                    comboBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
                    // create a combobox
                    FrameworkElementFactory comboFactory = new FrameworkElementFactory(typeof(ComboBox));
                    comboFactory.SetValue(ComboBox.IsTextSearchEnabledProperty, true);
                    comboFactory.SetValue(ComboBox.ItemsSourceProperty, _availableEmployeeList);
                    comboFactory.SetValue(ComboBox.SelectedValuePathProperty, "EmployeeID");
                    comboFactory.SetValue(ComboBox.DisplayMemberPathProperty, "ShortName");
                    comboFactory.SetBinding(ComboBox.SelectedValueProperty, comboBinding);
                    comboFactory.AddHandler(LoadedEvent, new RoutedEventHandler(GridEditCombo_Loaded));
                    DataTemplate comboTemplate = new DataTemplate();
                    comboTemplate.VisualTree = comboFactory;

                    // Set the Templates to the Column
                    storeColumns[i].CellTemplate = textTemplate;
                    storeColumns[i].CellEditingTemplate = comboTemplate;

                    // prepare a style for the main cells
                    Style mainColsStyle = new Style(typeof(DataGridCell));
                    // define the background color
                    Setter bgColorSetter = new Setter();
                    bgColorSetter.Property = DataGridCell.BackgroundProperty;
                    SolidColorBrush bgColorBrush = new SolidColorBrush();
                    Binding bgColorBinding = new Binding($"ScheduleListForTheDay[{i}].DisplayColor");
                    // => this causes the slow down and errors
                    BindingOperations.SetBinding(bgColorBrush, SolidColorBrush.ColorProperty, bgColorBinding);
                    bgColorSetter.Value = bgColorBrush;
                    mainColsStyle.Setters.Add(bgColorSetter);
                    storeColumns[i].CellStyle = mainColsStyle;

                    dgSchedule.Columns.Add(storeColumns[i]);

Does anyone have an idea ? I have been struggling with this for several weeks and can't seem to find a solution.

My datagrid's data source:

public List<WorkScheduleDay> _myWorkDataSource = new List<WorkScheduleDay>();

My WorkScheduleDay class:

    public string Day { get; set; }
    public DateTime Date { get; set; }
    public bool IsHoliday { get; set; }
    public ObservableCollection<ScheduleUnit> ScheduleListForTheDay { get; set; }

... and finally the ScheduleUnit class:

public DateTime Date { get; set; }
    public int StoreID { get; set; }
    private int _employeeID;
    public int EmployeeID
    {
        get { return _employeeID; }
        set
        {
            if (_employeeID != value)
            {
                _employeeID = value;
                // employee was changed: set confirmation status to false
                ScheduleConfirmed = false;
            }
        }
    }
    private string _displayText;
    public string DisplayText
    {
        get { return _displayText; }
        set
        {
            if (_displayText != value)
            {
                _displayText = value;
                RaisePropertyChanged();
            }
        }
    }
    private Color _displayColor;
    [JsonIgnore]
    public Color DisplayColor
    {
        get { return _displayColor; }
        set
        {
            if (_displayColor != value)
            {
                _displayColor = value;
                RaisePropertyChanged();
            }
        }
    }

Any help would be highly appreciated !

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,710 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,636 questions
0 comments No comments
{count} votes

3 answers

Sort by: Most helpful
  1. DaisyTian-1203 11,621 Reputation points
    2021-03-16T03:16:24.797+00:00

    What is your storeColumns? Is it type List<DataGridTemplateColumn>? If it is, how do you add data to storeColumns ? If you do not give data to it first, you may meet some error. I tested your code with some updates and used storeColumns as DataGridTemplateColumn storeColumns = new DataGridTemplateColumn();, then can implement background.

     public void testc()  
            {  
                List<ScheduleUnit> _myWorkDataSource = new List<ScheduleUnit>()  
                {  
                    new ScheduleUnit(){EmployeeID=001,Date=System.DateTime.Now,DisplayText="Text001",StoreID=0001,DisplayColor=Color.FromRgb(255,0,0)},  
                    new ScheduleUnit(){EmployeeID=002,Date=System.DateTime.Now,DisplayText="Text002",StoreID=0002,DisplayColor=Color.FromRgb(255,255,0)}  
                };  
      
                dgSchedule.ItemsSource = _myWorkDataSource;  
      
                //List<DataGridTemplateColumn> storeColumns = new List<DataGridTemplateColumn>();  
                DataGridTemplateColumn storeColumns = new DataGridTemplateColumn();  
      
                // create a textblock  
                FrameworkElementFactory textFactory = new FrameworkElementFactory(typeof(TextBlock));  
      
                textFactory.SetBinding(TextBlock.TextProperty, new Binding("EmployeeID"));  
                //textFactory.SetValue(HorizontalAlignmentProperty, HorizontalAlignment.Center);  
      
                DataTemplate textTemplate = new DataTemplate();  
                textTemplate.VisualTree = textFactory;  
      
                // Set the Templates to the Column  
                storeColumns.CellTemplate = textTemplate;  
      
                // prepare a style for the main cells  
                Style mainColsStyle = new Style(typeof(DataGridCell));  
                // define the background color  
                Setter bgColorSetter = new Setter();  
                bgColorSetter.Property = DataGridCell.BackgroundProperty;  
                SolidColorBrush bgColorBrush = new SolidColorBrush();  
                Binding bgColorBinding = new Binding("DisplayColor");  
                // => this causes the slow down and errors  
                BindingOperations.SetBinding(bgColorBrush, SolidColorBrush.ColorProperty, bgColorBinding);  
                bgColorSetter.Value = bgColorBrush;  
                mainColsStyle.Setters.Add(bgColorSetter);  
                storeColumns.CellStyle = mainColsStyle;  
                storeColumns.Header = "Mycustom column";  
      
                dgSchedule.Columns.Add(storeColumns);  
      
            }  
    

    The result picture is:
    78063-capture.png

    Did it do any help for your analysis? If it doesn't, please let me know and give me more info.


    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.

  2. Cyril77 1 Reputation point
    2021-03-16T08:39:53.2+00:00

    Hi Daisy,

    many thanks for getting back to me, if feels good to be able to talk to somebody about this after so many headaches :)

    To your questions:

    • storeColumns are indeed what you suggested: List<DataGridTemplateColumn> storeColumns = new List<DataGridTemplateColumn>();
    • adding data: I proceed in the following sequence
      1. first set the ItemsSource property to null
      2. then I build up the storeColumns in code
      3. then I have other methods that basically build the datasource and assign it as the ItemsSource _myWorkDataSource.Clear();
        ...
        for (DateTime scheduleDay = _startDate; scheduleDay <= _endDate; scheduleDay = scheduleDay.AddDays(1))
        {
        ...
        ObservableCollection<Holiday> scheduleListForTheDay = new ObservableCollection<Holiday>();
        ...
        _myWorkDataSource.Add(new WorkScheduleDay(scheduleDay, scheduleListForTheDay, scheduleDayIsHoliday));
        ...}
        ...
        dgSchedule.ItemsSource = _myWorkDataSource;

    Maybe some other relevant information is that this DataGrid is inside a User Control that is itself put inside a ContentControl by code.
    I do have several such user controls that I swap based on a main navigation menu with buttons.

    The initial set up is done for all user control in one go, while none of them is in the ContentControl:

                // WorkSchedule
                workScheduleUC = new ScheduleManagerUC(ScheduleManagerUC.ScheduleType.WorkSchedule);
                workScheduleUC.SetDataModel(appData, storeList, employeeList, businessYears, scheduleUnitList);
                workScheduleUC.UpdateScheduleNotes();
                workScheduleUC.LayoutScheduleGrid(); // this is where I create storeColumns
                workScheduleUC.PrefillScheduleRows(); // this is where I set a "blank" datasource with the appropriate number of rows and some empty data
                workScheduleUC.PopulateWorkSchedule(); // this is where I place the actual data in the grid
    

  3. Cyril77 1 Reputation point
    2021-03-19T12:20:32.683+00:00

    Hi @DaisyTian-MSFT,

    The _availableEmployeeList is: public ObservableCollection<Employee> _availableEmployeeList;
    It holds all employees that can be selected for a given day and should appear in the combobox.

    I am not sure I understand your question about the fields for "NEU" etc.
    I will try to make a minimal app based on my project that replicates the issue and will then share it here.

    Meanwhile, I was able to find a partial workaround:
    instead of:

    • changing the background color of the DataCell in my DataTemplate,
      I tried the following:
    • first adding a Grid as the container of the DataTemplate, and changing the Grid's background color
    • then adding the TextBlock as a child of the Grid.

    See the code here:

    // create a cell template
    // ----------------------------
    // parent element is a Grid
    FrameworkElementFactory gridFactory = new FrameworkElementFactory(typeof(Grid));
    // binding
    SolidColorBrush bgColorBrush = new SolidColorBrush();
    Binding bgColorBinding = new Binding($"ScheduleListForTheDay[{i}].DisplayColor");
    BindingOperations.SetBinding(bgColorBrush, SolidColorBrush.ColorProperty, bgColorBinding);
    gridFactory.SetValue(BackgroundProperty, bgColorBrush);
    
    // create a textblock in the grid
    FrameworkElementFactory textFactory = new FrameworkElementFactory(typeof(TextBlock));
    // binding
    Binding textBinding = new Binding($"ScheduleListForTheDay[{i}].DisplayText");
    textBinding.Mode = BindingMode.OneWay;
    textFactory.SetBinding(TextBlock.TextProperty, textBinding);
    textFactory.SetValue(HorizontalAlignmentProperty, HorizontalAlignment.Center);
    textFactory.SetValue(FontWeightProperty, FontWeights.Bold);
    
    // create the template and attach the controls to its VisualTree
    DataTemplate normalTemplate = new DataTemplate(typeof(Grid));
    gridFactory.AppendChild(textFactory);
    normalTemplate.VisualTree = gridFactory;
    

    This solved 95% of the binding failures, and reduces the slowdown accordingly.
    My tentative interpretation is that virtualization doesn't properly handle direct bindings to the DataCell's properties, hence the "target not found" message.
    What do you think ?