前往
大廳
主題

資料結構(INotifyPropertyChanged)與DataGrid建立關聯檢視

Yang | 2022-07-14 19:41:09 | 巴幣 0 | 人氣 200

接續之前的ObservableCollection<string>,在此紀錄動態資料ObservableCollection<INotifyPropertyChanged>的簡單範例,以常見的程式log舉例
上圖是之前字串轉列舉(static Enum ConvertTo(this string obj, Type enumType))範例的log

XAML:
<TabItem Header="AppLog">
    <Grid Background="#FFE5E5E5">
        <DataGrid x:Name="DataGridAppLog" Margin="0" AutoGenerateColumns="False" CanUserAddRows="False" SelectionMode="Extended" SelectionUnit="Cell" IsSynchronizedWithCurrentItem="True">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding CreatedTime}"/>
                <DataGridTextColumn Binding="{Binding Level}"/>
                <DataGridTextColumn Binding="{Binding ThreadID}"/>
                <DataGridTextColumn Binding="{Binding CallerLineNumber}"/>
                <DataGridTextColumn Binding="{Binding CallerMemberName}"/>
                <DataGridTextColumn Binding="{Binding Message}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</TabItem>

C#:
[Serializable]
class AppLog : NotifyPropertyChanged
{
    //利用屬性名稱(PropertyInfo.Name)建立AppLog的欄位特性(ColumnAttribute)索引
    public static readonly Dictionary<string, (ColumnAttribute, PropertyInfo)> PropertyMap = typeof(AppLog).GetColumnAttrMapByProperty<ColumnAttribute>(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.SetProperty);

    [Column("建立者", CSVIndex = -1)]
    public string Creator { get; set; }

    [Column("日期", CSVIndex = -1)]
    public DateTime CreatedDate => CreatedTime.Date;

    [Column("時間", WPFDisplayIndex = 0, WPFStringFormat = "{0:HH:mm:ss.fff}")]
    public DateTime CreatedTime
    {
        get { return _createdTime; }
        set { OnPropertiesChanged(ref _createdTime, value, nameof(CreatedTime), nameof(CreatedDate)); } //多欄位資料異動
    }

    [Column("等級", WPFDisplayIndex = 1)]
    public string Level { get; set; }

    [Column("執行緒", "緒", WPFDisplayIndex = 2, WPFHorizontalAlignment = WPFHorizontalAlignment.Right)]
    public int ThreadID { get; set; }

    [Column("訊息", WPFDisplayIndex = 5)]
    public string Message { get; set; }

    [Column("原始碼行號", "行", WPFDisplayIndex = 3, WPFHorizontalAlignment = WPFHorizontalAlignment.Right)]
    public int CallerLineNumber { get; set; }

    [Column("呼叫端方法或屬性名稱", WPFDisplayIndex = 4, WPFForeground = "MediumBlue")]
    public string CallerMemberName { get; set; }

    public AppLog([CallerMemberName] string memberName = "")
    {
        Creator = memberName;
        CreatedTime = DateTime.Now;
        Level = string.Empty;
        ThreadID = 0;
        Message = string.Empty;
        CallerLineNumber = 0;
        CallerMemberName = string.Empty;
    }
}

//根據資料結構的欄位特性(ColumnAttribute)描述,為DataGrid的欄位(DataGridTextColumn)建立初始設定
MainWindow.DataGridAppLog.SetHeadersByBindings(AppLog.PropertyMap.Values.ToDictionary(x => x.Item2.Name, x => x.Item1));

//建立AppLog動態資料集,取得資料集的檢視物件,與DataGrid建立關聯檢視
ObservableCollection<AppLog> _appLogCollection = MainWindow.DataGridAppLog.SetViewAndGetObservation<AppLog>();

//欄位特性,非正規XAML寫法,將CSV檔和DataGrid的欄位元資料(metadata)設定收攏在程式碼的同一個區塊,有需要也能再擴充,譬如將DataTable和Excel的欄位元資料收攏,日後資料的紀錄與呈現有需要調整時,各種不同的載體能在同一個程式碼區塊內一起調整
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
class ColumnAttribute : Attribute
{
    public string CSVName { get; set; }
    public int CSVIndex { get; set; }
    public string CSVStringFormat { get; set; }

    public string WPFName { get; set; }
    public int WPFDisplayIndex { get; set; }
    public string WPFStringFormat { get; set; }
    public bool WPFIsReadOnly { get; set; }
    public WPFVisibility WPFVisibility { get; set; }
    public bool WPFCanUserReorder { get; set; }
    public bool WPFCanUserSort { get; set; }
    public WPFHorizontalAlignment WPFHorizontalAlignment { get; set; }
    public string WPFForeground { get; set; }

    public ColumnAttribute(string csvName, string wpfName)
    {
        CSVName = csvName;
        CSVIndex = 0;
        CSVStringFormat = string.Empty;

        WPFName = wpfName;
        WPFDisplayIndex = -1;
        WPFStringFormat = string.Empty;
        WPFIsReadOnly = true;
        WPFVisibility = WPFVisibility.Visible;
        WPFCanUserReorder = true;
        WPFCanUserSort = false;
        WPFHorizontalAlignment = WPFHorizontalAlignment.Left;
        WPFForeground = string.Empty; //"MediumBlue";
    }

    public ColumnAttribute(string csvName) : this(csvName, csvName)
    {
        //
    }

    public ColumnAttribute() : this(string.Empty, string.Empty)
    {
        //
    }
}

//利用屬性名稱(PropertyInfo.Name)建立指定資料結構的欄位特性(ColumnAttribute)索引
static Dictionary<string, (T, PropertyInfo)> GetColumnAttrMapByProperty<T>(this Type obj, BindingFlags flags) where T : ColumnAttribute
{
    Dictionary<string, (T, PropertyInfo)> result = new Dictionary<string, (T, PropertyInfo)>();
    PropertyInfo[] piArr = obj.GetProperties(flags);

    foreach (PropertyInfo pi in piArr)
    {
        try
        {
            Attribute attr = Attribute.GetCustomAttribute(pi, typeof(T), false);

            if (attr is T column)
            {
                result.Add(pi.Name, (column, pi));
            }
        }
        catch (ArgumentException ex)
        {
            throw new ArgumentException($"{pi.Name}|{ex.Message}");
        }
        catch
        {
            throw;
        }
    }

    return result;
}

//根據欄位特性(ColumnAttribute),為DataGrid的欄位(DataGridTextColumn)建立初始設定
static void SetHeadersByBindings<T>(this DataGrid obj, IDictionary<string, T> propertyNameMap) where T : ColumnAttribute
{
    foreach (DataGridColumn column in obj.Columns)
    {
        if (column is DataGridBoundColumn bound && bound.Binding is Binding bind)
        {
            if (propertyNameMap.TryGetValue(bind.Path.Path, out T attr))
            {
                column.Header = attr.WPFName;
                column.DisplayIndex = attr.WPFDisplayIndex;
                bind.StringFormat = attr.WPFStringFormat;
                column.IsReadOnly = attr.WPFIsReadOnly;
                column.Visibility = attr.WPFVisibility.ToString().ConvertTo<Visibility>();
                column.CanUserReorder = attr.WPFCanUserReorder;
                column.CanUserSort = attr.WPFCanUserSort;

                column.Width = new DataGridLength(1.0, DataGridLengthUnitType.Auto);

                Style headerS = new Style(typeof(DataGridColumnHeader));
                headerS.Setters.Add(new Setter(ToolTipService.ToolTipProperty, $"{column.DisplayIndex},{attr.CSVName},{bind.Path.Path},{bind.StringFormat}"));
                column.HeaderStyle = headerS;

                if (column is DataGridTextColumn col)
                {
                    Style elementS = null;

                    if (attr.WPFHorizontalAlignment != WPFHorizontalAlignment.Left)
                    {
                        elementS = new Style();
                        elementS.Setters.Add(new Setter(FrameworkElement.HorizontalAlignmentProperty, attr.WPFHorizontalAlignment.ToString().ConvertTo<HorizontalAlignment>()));
                    }

                    if (!string.IsNullOrWhiteSpace(attr.WPFForeground))
                    {
                        if (elementS == null)
                        {
                            elementS = new Style();
                        }

                        elementS.Setters.Add(new Setter(TextBlock.FontWeightProperty, FontWeights.DemiBold));
                        elementS.Setters.Add(new Setter(TextBlock.ForegroundProperty, new BrushConverter().ConvertFromString(attr.WPFForeground)));
                    }

                    if (elementS != null)
                    {
                        col.ElementStyle = elementS;
                    }
                }
            }
        }
    }
}

以上是很複雜的簡單範例,大部分方法打包成共用程式碼,設計其他資料結構時,只要遵循序列化([Serializable]),繼承NotifyPropertyChanged,建立欄位特性(ColumnAttribute)索引PropertyMap的原則,就能專心在資料結構的設計上,專心在單一程式碼區塊上

其他像是初始化DataGrid的欄位(SetHeadersByBindings),或是建立動態資料集(ObservableCollection<INotifyPropertyChanged>)與DataGrid的關聯檢視,能一行程式碼完成
送禮物贊助創作者 !
0
留言

創作回應

更多創作