question

Cyril77-9561 avatar image
0 Votes"
Cyril77-9561 asked Cyril77-9561 commented

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

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 !


dotnet-csharpwindows-wpf
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

DaisyTian-1203 avatar image
1 Vote"
DaisyTian-1203 answered Cyril77-9561 commented

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.


capture.png (10.0 KiB)
· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi,

Please see my comments here and in the other answer...

Additionally, I tried adding some data before adding the storeColumns, instead of setting ItemsSource to null.

 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;

Alas, this doesn't solve the binding failures:
- After startup:
78155-bindingfailures1.png
- After replacing the datagrid's user control in the ContentControl by another user control:
78202-bindingfailures2.png


0 Votes 0 ·
Cyril77-9561 avatar image
0 Votes"
Cyril77-9561 answered DaisyTian-1203 commented

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


· 8
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@Cyril77-9561
I don't not know what kind of DataGrid are you trying to implement, could you give me a brief description? Could you give me some explanations of Binding($"ScheduleListForTheDay[{i}].DisplayText") ?

0 Votes 0 ·

@DaisyTian-MSFT,

the main goal of the datagrid is the following:

  • rows are dates (between a start and end date).

  • columns represent physical stores (like bookshop) (after the first three indicating: date, day of the week and whether it's a holiday)

  • the user can thus select an employee who should work, a given day, at a given store.

  • each employee has a custom "color" attached

  • the cell is colored based on the selected employee's color (DisplayColor)

  • the cell's text is "DisplayText", which is the "ShortName" of the employee.

The stores are dynamically inputted by the user and there is an arbitrary number of them (my client has currently 7, but that is subject to change in the future).

When the employee is selected, I retrieve (in the "Edit Ending" event) his/her name and color, and copy it over to the ScheduleUnit in ScheduleListForTheDay[{i}] (where "i" is the number of the store/column).

0 Votes 0 ·

@DaisyTian-MSFT,

One extra point: before the allocation (through the cell's combobox edit template), I fill the ScheduleListForTheDay[] with "fake", "empty" ScheduleUnits.
White color for regular days for which the employee is not yet selected
Black color for holidays.

Please see the following screenshot:

78756-schedulegrid.png

(the columns which checkmarks indicate whether the employee confirmed the schedule for that day)

0 Votes 0 ·
schedulegrid.png (38.5 KiB)
Show more comments
Cyril77-9561 avatar image
0 Votes"
Cyril77-9561 answered Cyril77-9561 commented

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 ?

· 3
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@Cyril77-9561
I use Setter bgColorSetter = new Setter(DataGridCell.BackgroundProperty, new SolidColorBrush(Color.FromRgb(221, 221, 221))); to create the bgColorSetter like below , the binding error will disappear. There may be somethinf wrong of your bgColorBinding's conversion. Could you try using SolidColorBrush(Color) ?


   Style mainColsStyle = new Style(typeof(DataGridCell));
                         Setter bgColorSetter = new Setter(DataGridCell.BackgroundProperty, new SolidColorBrush(Color.FromRgb(221, 221, 221)));
                         mainColsStyle.Setters.Add(bgColorSetter);
                         mainColsStyle.Setters.Add(new Setter(FontWeightProperty, FontWeights.Bold));
                         storeColumns[i].CellStyle = mainColsStyle;
                         dgSchedule.Columns.Add(storeColumns[i]);
0 Votes 0 ·

@DaisyTian-MSFT,

I don't see any binding in your code ? If I use only your code, all cells are simply colored grey. If I add my DisplayColor binding (between lines 2 and 3 of your code), the errors are still there.

 Style mainColsStyle = new Style(typeof(DataGridCell));
 Setter bgColorSetter = new Setter(DataGridCell.BackgroundProperty, new SolidColorBrush(Color.FromRgb(221, 221, 221)));
 SolidColorBrush bgColorBrush = new SolidColorBrush();
 Binding bgColorBinding = new Binding($"ScheduleListForTheDay[{i}].DisplayColor");
 BindingOperations.SetBinding(bgColorBrush, SolidColorBrush.ColorProperty, bgColorBinding);
 bgColorSetter.Value = bgColorBrush;
 mainColsStyle.Setters.Add(bgColorSetter);
 mainColsStyle.Setters.Add(new Setter(FontWeightProperty, FontWeights.Bold));
 storeColumns[i].CellStyle = mainColsStyle;
 dgSchedule.Columns.Add(storeColumns[i]);


Do you have any insights of what the error in the binding code could be ?

0 Votes 0 ·

Hi @DaisyZhou-MSFT ,

do you have any news about this for me ? As the behavior is so different between the DataCell's background color and a grid's or a rectangle's, it does look like a bug to me. Is there somewhere I could/should report it ?

Cyril

0 Votes 0 ·