I'm developing a WinUI 3 app that displays a dynamic grid (using Syncfusion’s SfDataGrid) of employees and their SOP statuses. Each employee has a set of SOP statuses stored in a dictionary (keyed by normalized SOP names). For every master SOP, a corresponding grid column is created dynamically so that each cell displays a tick (✔) if the employee has that SOP (and a clickable button to open the file) or a cross (❌) if not.
What I'm trying to achieve:
For each employee, compare the SOP files in their folder to a master list of SOPs. Save the status (tick or cross) plus the file path (if available) in a dictionary property of the employee model. Dynamically generate grid columns where each column’s cell template binds to the dictionary value (an object containing a “Status” and “FilePath” property). In the cell template, display a Button (with tick) if the status is ticked; otherwise display a TextBlock showing a cross. The problem:
Even though my debug output confirms that—for example—the “DFL De-Fillet Line Outfeed Operations.doc” SOP is found for an employee (and its normalized key is present in the dictionary with Status = "✔"), the grid still shows a cross. Also, I keep getting binding errors such as:
"BindingExpression path error: 'Status' property not found on 'MyNamespace.CasualEmployeeSkill'" "BindingExpression path error: 'FilePath' property not found on 'MyNamespace.CasualEmployeeSkill'"
It appears that the binding isn’t reaching the dictionary’s value (the SOPStatusEntry) but instead is trying to find a “Status” or “FilePath” on the employee object itself.
Employee Model and Status Entry:
public class SOPStatusEntry
{
public string Status { get; set; } = "❌"; // Default value
public string FilePath { get; set; } = string.Empty; // Path to the file if available
}
public class CasualEmployeeSkill : INotifyPropertyChanged
{
public string EmployeeName { get; set; }
// Dictionary mapping normalized SOP names to their status entry
private Dictionary<string, SOPStatusEntry> _sopStatus = new Dictionary<string, SOPStatusEntry>();
public Dictionary<string, SOPStatusEntry> SOPStatus
{
get => _sopStatus;
set { _sopStatus = value; OnPropertyChanged(nameof(SOPStatus)); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifySOPStatusChanged() => OnPropertyChanged(nameof(SOPStatus));
protected void OnPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Dynamic Column Creation in Code-Behind:
// For each master SOP, add a column so that the cell’s DataContext is the dictionary value.
foreach (var sop in MasterSOPs)
{
string columnName = Normalize(sop.Name);
SkillMatrixGrid.Columns.Add(new Syncfusion.UI.Xaml.DataGrid.GridTemplateColumn
{
// IMPORTANT: Set MappingName to "SOPStatus[normalizedKey]"
MappingName = $"SOPStatus[{columnName}]",
HeaderText = sop.Name,
HeaderTemplate = (DataTemplate)this.Resources["SOPHeaderTemplate"],
CellTemplate = (DataTemplate)this.Resources["SOPCellTemplate"]
});
}
XAML Cell Template Example:
<DataTemplate x:Key="SOPCellTemplate">
<Grid>
<!-- Button is shown if Status equals "✔"; otherwise TextBlock (with ❌) is shown.
Note: The DataContext for the cell is the dictionary value (an SOPStatusEntry) -->
<Button Content="✔"
Visibility="{Binding Status, Converter={StaticResource ButtonVisibilityConverter}}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Click="OpenFile_ClickEvent"
Tag="{Binding FilePath}" />
<TextBlock Text="❌"
Visibility="{Binding Status, Converter={StaticResource TextVisibilityConverter}}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</DataTemplate>
Converters (simplified examples):
public class ButtonVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is string status)
return status == "✔" ? Visibility.Visible : Visibility.Collapsed;
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
public class TextVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is string status)
return status == "✔" ? Visibility.Collapsed : Visibility.Visible;
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Key points:
Dictionary Binding: The grid column’s MappingName must be set so that the cell’s DataContext is the dictionary value (an instance of SOPStatusEntry). Notice that in the code above, we use: MappingName = $"SOPStatus[{columnName}]" (Do not append “.Status” in the MappingName.)
Cell Template Binding: In the cell template, we bind directly to {Binding Status} and {Binding FilePath}. This works because the DataContext for each cell is the dictionary value from SOPStatus.
Dynamic Key: The normalized SOP name (calculated in the code-behind via the Normalize() method) is used as the key for each dictionary entry. This key must match exactly between the dynamic column definition and the dictionary populated for each employee.
Converter Errors: The error messages indicate that the binding is trying to find the properties on the wrong object. By setting the MappingName without “.Status”, the cell’s DataContext becomes the correct SOPStatusEntry. (For example, if the normalized key is “siteforkliftgeneraldoc”, then the binding in the cell template should resolve to the SOPStatusEntry corresponding to SOPStatus["siteforkliftgeneraldoc"]).
My Question: Despite setting the MappingName correctly and ensuring that the cell template binds to the SOPStatusEntry properties, I still see errors like "BindingExpression path error: 'Status' property not found on 'CasualEmployeeSkill'." It seems the binding isn’t picking up the dictionary value correctly. Has anyone encountered this issue? How can I ensure that the dynamic grid column binds to the dictionary value (SOPStatusEntry) and not to the parent employee object?
Any help or insight would be greatly appreciated!
What did you try and what were you expecting? I tried binding my cell templates to the dictionary inside my CasualEmployeeSkill class. For example, I attempted to use bindings like:
<Button Content="✔"
Visibility="{Binding SOPStatus[siteforkliftgeneraldoc].Status, Converter={StaticResource ButtonVisibilityConverter}}"
Tag="{Binding SOPStatus[siteforkliftgeneraldoc].FilePath}" />
<TextBlock Text="❌"
Visibility="{Binding SOPStatus[siteforkliftgeneraldoc].Status, Converter={StaticResource TextVisibilityConverter}}" />
I expected the grid to show a tick if the employee’s SOP folder contained a file that loosely matches the master SOP name, and a cross otherwise. When a tick is shown, clicking it should open the file. Despite multiple attempts (and even trying dynamic keys), I continuously get binding errors where it seems the binding engine is searching for the “Status” and “FilePath” properties on the employee object rather than on the dictionary entry (SOPStatus) associated with each SOP.
I’ve also tried modifying the cell template and adjusting my code (for example, removing “.Status” from the MappingName in code-behind), but the error persists. Any help to correctly bind to a dynamic dictionary property and make the cell button visible only when the SOP status is tick (and to pass the file path) would be greatly appreciated.
Edit : Added MRE
Note - synfusion.core.winui & synfusion.grid.winui packages must be installed
MainPage.xaml
<Page
x:Class="Test.MainPage"
xmlns=";
xmlns:x=";
xmlns:syncfusion="using:Syncfusion.UI.Xaml.DataGrid"
xmlns:local="using:Test">
<Page.Resources>
<local:ButtonVisibilityConverter x:Key="ButtonVisibilityConverter"/>
<local:TextVisibilityConverter x:Key="TextVisibilityConverter"/>
<DataTemplate x:Key="SOPCellTemplate">
<Grid>
<!-- Button if SOP present -->
<Button Content="✔"
Visibility="{Binding Status, Converter={StaticResource ButtonVisibilityConverter}}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<!-- Text if SOP not present -->
<TextBlock Text="❌"
Visibility="{Binding Status, Converter={StaticResource TextVisibilityConverter}}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</DataTemplate>
</Page.Resources>
<Grid Background="Black">
<syncfusion:SfDataGrid x:Name="MyDataGrid" AutoGenerateColumns="False"/>
</Grid>
</Page>
MainPage.xaml.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Text.RegularExpressions;
namespace Test
{
public sealed partial class MainPage : Page
{
public ObservableCollection<Employee> Employees { get; set; } = new ObservableCollection<Employee>();
public List<string> MasterSOPs { get; set; } = new List<string>
{
"SOP1",
"SOP2"
};
public MainPage()
{
this.InitializeComponent();
LoadData();
}
private void LoadData()
{
// create a test employee.
var emp = new Employee { EmployeeName = "John Doe" };
// Populate the employee's dictionary for each master SOP.
foreach (var sop in MasterSOPs)
{
string key = Normalize(sop);
// For testing John Doe has SOP1 ("✔") and not SOP2 ("❌").
emp.SOPStatus[key] = new SOPStatusEntry
{
Status = sop == MasterSOPs.First() ? "✔" : "❌",
FilePath = sop == MasterSOPs.First() ? @"C:\Test\TestFile.doc" : string.Empty
};
}
Employees.Add(emp);
// Set up grid columns.
MyDataGrid.Columns.Clear();
// Add Employee Name column.
MyDataGrid.Columns.Add(new Syncfusion.UI.Xaml.DataGrid.GridTextColumn
{
MappingName = "EmployeeName",
HeaderText = "Employee Name",
Width = 200
});
// For each master SOP, add a column.
foreach (var sop in MasterSOPs)
{
string key = Normalize(sop);
MyDataGrid.Columns.Add(new Syncfusion.UI.Xaml.DataGrid.GridTemplateColumn
{
MappingName = $"SOPStatus[{key}]", // MappingName to dictionary entry
HeaderText = sop,
CellTemplate = (DataTemplate)this.Resources["SOPCellTemplate"]
});
}
// Bind the collection to the grid.
MyDataGrid.ItemsSource = Employees;
}
private string Normalize(string input) =>
Regex.Replace(input, @"[^a-zA-Z0-9]", "").ToLower();
}
// Employee model.
public class Employee : INotifyPropertyChanged
{
public string EmployeeName { get; set; }
// Dictionary mapping normalized SOP names to SOPStatusEntry.
private Dictionary<string, SOPStatusEntry> _sopStatus = new Dictionary<string, SOPStatusEntry>();
public Dictionary<string, SOPStatusEntry> SOPStatus
{
get => _sopStatus;
set { _sopStatus = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
// Represents the status of an SOP.
public class SOPStatusEntry
{
public string Status { get; set; } // "✔" if present, "❌" if missing
public string FilePath { get; set; } // File path if available
}
// Converters to show/hide button or text depending on status.
public class ButtonVisibilityConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, string language)
{
return value is string status && status == "✔" ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, System.Type targetType, object parameter, string language)
{
throw new System.NotImplementedException();
}
}
public class TextVisibilityConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, string language)
{
return value is string status && status == "✔" ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, System.Type targetType, object parameter, string language)
{
throw new System.NotImplementedException();
}
}
}
Thanks!
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744831330a4596101.html
评论列表(0条)