具有RichText、RowDetails、Grouping、Filter等功能的WPF DataGrid!

目录

介绍

背景

使用代码

概述

分组和筛选

RowDetails 区域

超链接扩展

文本搜索和过滤

MVVM

集合视图和参数命令示例

使用应用程序

上下文菜单

致谢/参考资料


介绍

本文和代码片段展示了DataGridRowDetailsGroupingFilter如何使用xml文件作为数据源。

背景

该项目基于MS Learn示例。

使用代码

概述

分组筛选

MS Learn示例[1]中进行了详细介绍和解释。

我添加了简单文本搜索添加新行按钮。

RowDetails 区域

这是在ResourceDictionary中定义的。

<ResourceDictionary 
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="clr-namespace:DataGridUC1"
                xmlns:local2="clr-namespace:DataGridUC1.Controls" 
                xmlns:b="http://schemas.microsoft.com/xaml/behaviors" 
                xmlns:ViewModel="clr-namespace:DataGridUC1.ViewModel">

    <Style x:Key="DataGridCellStyle" 
         TargetType="{x:Type DataGridCell}">
        <Style.Triggers>
            <Trigger Property="IsSelected"
               Value="True">
                <Setter Property="BorderBrush" Value="Transparent"/>
                <Setter Property="Foreground"
                Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
                <Setter Property="Background" Value="Yellow"/>
            </Trigger>
        </Style.Triggers>
    </Style>

    <DataTemplate x:Key="DataGridPlusRowDetailsTemplate">
        <StackPanel HorizontalAlignment="Stretch" 
                    Height="225" Orientation="Vertical" Width="NaN" Margin="31,0,0,0" 
                    Background="{DynamicResource {x:Static SystemColors.InfoBrushKey}}">
            
            <Label Content="Group" HorizontalAlignment="Left" 
                   FontSize="14" FontWeight="Bold" />
            <TextBox x:Name="Item"
                   Text="{Binding Item, Mode=TwoWay, 
                            UpdateSourceTrigger=PropertyChanged}"
                    Margin="5,1,3,2"
                    IsEnabled="True" ToolTip="Item" 
                    HorizontalAlignment="Left" MinWidth="50" />
            <Label Content="Note" HorizontalAlignment="Left" 
                    FontSize="14" FontWeight="Bold" />
            <TextBox x:Name="Note"
                    Margin="5,1,3,2"
                    Text="{Binding Note, Mode=TwoWay, 
                            UpdateSourceTrigger=PropertyChanged}"
                    ToolTip="Note" IsEnabled="True" MinWidth="50" />

            <StackPanel Orientation="Horizontal" Height="32" 
                    VerticalAlignment="Stretch" 
                    HorizontalAlignment="Left" Width="500" >
                <Label Content="Check" HorizontalAlignment="Left" 
                        VerticalAlignment="Bottom" HorizontalContentAlignment="Center" 
                        VerticalContentAlignment="Center" 
                        FontSize="14" FontWeight="Bold" />
                <CheckBox
                        Margin="10,8,3,2"
                        IsChecked="{Binding Check, Mode=TwoWay, 
                                UpdateSourceTrigger=PropertyChanged}"
                        ToolTip="CheckBox" IsEnabled="True" 
                        AutomationProperties.HelpText="Check" 
                        HorizontalAlignment="Left" VerticalAlignment="Center" 
                        VerticalContentAlignment="Center" />

                <Label Content="Rating" HorizontalAlignment="Center" 
                       VerticalAlignment="Bottom" HorizontalContentAlignment="Right" 
                       VerticalContentAlignment="Center" Width="184" 
                       FontSize="14" FontWeight="Bold" />
                <ComboBox
                        Margin="22,10,3,2"
                        Text="{Binding Rating, Mode=TwoWay, 
                                UpdateSourceTrigger=PropertyChanged}"
                        ToolTip="ComboBox" 
                        IsEnabled="True" 
                        HorizontalAlignment="Right" VerticalAlignment="Bottom" 
                        Width="88" HorizontalContentAlignment="Center" 
                        VerticalContentAlignment="Center" >
                        <ComboBoxItem Content="Average"/>
                        <ComboBoxItem Content="Good"/>
                        <ComboBoxItem Content="Excellent"/>
                </ComboBox>
            </StackPanel>

            <Label Content="Link" HorizontalAlignment="Left" 
                   FontSize="14" FontWeight="Bold" />

             <TextBlock FontFamily="Segoe UI" FontSize="16">
                <Hyperlink NavigateUri="{Binding Text, ElementName=LinkTB, 
                    Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                           local2:HyperlinkExtensions.IsExternal="true">
                           --> Click here to fire the hyperlink 
                </Hyperlink>
            </TextBlock>

            <TextBox x:Name="LinkTB"
                Margin="5,1,3,2"
                Text="{Binding Link, Mode=TwoWay, 
                        UpdateSourceTrigger=PropertyChanged}"
                ToolTip="Link" IsEnabled="True" 
                Foreground="{DynamicResource {x:Static 
                        SystemColors.InfoTextBrushKey}}" 
                Background="{DynamicResource {x:Static 
                        SystemColors.ControlLightLightBrushKey}}" />

        </StackPanel>
    </DataTemplate>

</ResourceDictionary>

行详细信息包含用于编辑当前行的文本框和用于触发相关超链接测试按钮

超链接扩展

此扩展基于[3],并在Row Details区域中使用,如上所述。

namespace DataGridUC1.Controls
{
    // https://stackoverflow.com/questions/10238694/example-using-hyperlink-in-wpf

    public static class HyperlinkExtensions
    {
        public static bool GetIsExternal(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsExternalProperty);
        }

        public static void SetIsExternal(DependencyObject obj, bool value)
        {
            obj.SetValue(IsExternalProperty, value);
        }
        public static readonly DependencyProperty IsExternalProperty =
            DependencyProperty.RegisterAttached("IsExternal", typeof(bool), 
                typeof(HyperlinkExtensions), 
                new UIPropertyMetadata(false, OnIsExternalChanged));

        private static void OnIsExternalChanged(object sender, 
            DependencyPropertyChangedEventArgs args)
        {
            var hyperlink = sender as Hyperlink;

            if ((bool)args.NewValue)
                hyperlink.RequestNavigate += Hyperlink_RequestNavigate;
            else
                hyperlink.RequestNavigate -= Hyperlink_RequestNavigate;
        }

        private static void Hyperlink_RequestNavigate(object sender, 
            System.Windows.Navigation.RequestNavigateEventArgs e)
        {
            //Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
            //e.Handled = true;

// https://www.codeproject.com/Questions/5380961/How-do-I-fix-net-8-process-start-url-issue
            Hyperlink link = (Hyperlink)e.OriginalSource;
            Process? process = Process.Start(new ProcessStartInfo(link.NavigateUri.AbsoluteUri)
            {
                UseShellExecute = true
            });

            process!.WaitForExit();
            e.Handled = true;
        }
    }
}

文本搜索和过滤

MS Learn示例用于过滤选中/未选中的任务的方法激发了我创建以下超级简单的文本搜索

使用FilterEventArgs,我们获得每个任务/行的DataRowView,这允许我们对该方法使用简单的if语句。因此,逻辑存在于VM中。

MVVM

为了将对象/控件从 View 传递给 ViewModel,我们使用Interaction.TriggersParameterCommand

ViewModel 还包含用于读取/写入XML数据的逻辑。

private void LoadXML()
{
    _ds.Clear();
    _ds.ReadXml(_data.FullName);
}

// ds.WriteXml(path);
private void WriteXML()
{
    _ds.AcceptChanges();

    _ds.WriteXml(path);
MessageBox.Show("xml data saved. ");
}

筛选器文本搜索的逻辑

private void CompleteFilter_Changed(object sender, RoutedEventArgs e)
{
    if (sender != null)
    {
        cbCompleteFilter = (bool)((CheckBox)sender).IsChecked;
    }
    // Refresh the view to apply filters.
    cvs.View.Refresh();
}

private void CollectionViewSource_Filter(object sender, FilterEventArgs e)
{
    //Task t = e.Item as Task;
    DataRowView drv = e.Item as DataRowView;

    if (e.Item != null)
    {
        drv = (DataRowView)e.Item;

        if (drv != null && cbCompleteFilter != null)
        // If filter is turned on, filter completed items.
        {
            // if (this.cbCompleteFilter.IsChecked == true && t.Complete == true)
            if (this.cbCompleteFilter == true && (bool)drv.Row["Check"] == true)
            {
                e.Accepted = false;
            }
            else
                e.Accepted = true;
        }
    }
}

private void SearchBox_Changed(object sender, RoutedEventArgs e)
{
    if (sender != null)
    {
        searchBox = (TextBox)sender;
    }
    // Refresh the view to apply filters.
    cvs.View.Refresh();
}

private void CollectionViewSource_Search(object sender, FilterEventArgs e)
{
    DataRowView drv = e.Item as DataRowView;
    if (e.Item != null)
    {
        drv = (DataRowView)e.Item;

        if (drv != null && searchBox != null
            && this.cbCompleteFilter == false)
        {
            if (drv.Row["Item"].ToString().ToLower().Contains(searchBox.Text.ToLower()) == false
                && drv.Row["Note"].ToString().ToLower().Contains(searchBox.Text.ToLower()) == false)
            {
                e.Accepted = false;
            }
            else
                e.Accepted = true;
        }

        if (drv != null && searchBox != null
            && this.cbCompleteFilter == true)
        {
            if (drv.Row["Item"].ToString().ToLower().Contains(searchBox.Text.ToLower()) == false
                && drv.Row["Note"].ToString().ToLower().Contains(searchBox.Text.ToLower()) == false
                || (bool)drv.Row["Check"] == true)
            {
                e.Accepted = false;
            }
            else
                e.Accepted = true;
        }
    }
}

PropertiesICommandsParameterCommands始终以相同的方式工作,因此在下面显示一个示例就足够了。

集合视图和参数命令示例

我们使用文本搜索功能使其更清晰。

我们在XAML文件中使用Interaction.Triggers。这意味着每次当CollectionViewRefreshed时,都会处理过滤器。

当我们将Ds.Credits (表名)绑定为CollectinViewSource时,我们从XML文件中获取数据。

CollectionViewTypeListCollectionView

<CollectionViewSource x:Key="cvsTasks" Source="{Binding Ds.Credits}"
                      CollectionViewType="ListCollectionView" >

    <CollectionViewSource.SortDescriptions>

        <scm:SortDescription PropertyName="Item"/>
        <scm:SortDescription PropertyName="Check" />
        <!--<scm:SortDescription PropertyName="DueDate" />-->

    </CollectionViewSource.SortDescriptions>

    <CollectionViewSource.GroupDescriptions>
        <PropertyGroupDescription PropertyName="Item"/>
        <PropertyGroupDescription PropertyName="Check"/>
    </CollectionViewSource.GroupDescriptions>

    <b:Interaction.Triggers>
        <b:EventTrigger EventName= "Filter">
            <b:InvokeCommandAction Command="{Binding
                        ParameterCmdFilter, Mode=OneWay}"
                CommandParameter="{Binding cvsTasks,
                            RelativeSource={RelativeSource
                                AncestorType={x:Type CollectionViewSource}}}"
                            PassEventArgsToCommand="True" />
        </b:EventTrigger>
        <b:EventTrigger EventName= "Filter">
            <b:InvokeCommandAction Command="{Binding
                        ParameterCmdSearch, Mode=OneWay}"
                CommandParameter="{Binding cvsTasks,
                            RelativeSource={RelativeSource
                                AncestorType={x:Type CollectionViewSource}}}"
                            PassEventArgsToCommand="True" />
        </b:EventTrigger>
    </b:Interaction.Triggers>

</CollectionViewSource>

对于文本搜索,使用EventTrigger EventName= “Filter”,将调用Command ParameterCmdSearch,并且CommandParametercvsTasks(源Ds.Credits的密钥)。

然后,ParameterCmdSearch调用CollectionViewSource_Search。因此,我们将每个DataRow作为参数获取。

搜索TextBox的内容与另一个参数命令一起传递。

搜索string被转换ToLower,因此我们忽略UpperCase

<TextBox x:Name="SearchBox"
    Margin="5,1,3,2"
    IsEnabled="True" ToolTip="Item" HorizontalAlignment="Left"
        MinWidth="120" FontSize="14" AcceptsReturn="True"
        MaxLines="1" >

    <b:Interaction.Triggers>
        <b:EventTrigger EventName= "TextChanged">
            <b:InvokeCommandAction Command="{Binding
                      ParameterCmdSearchBox, Mode=OneWay}"
                CommandParameter="{Binding ElementName= SearchBox,
                    Mode=OneWay}"/>
        </b:EventTrigger>
    </b:Interaction.Triggers>

</TextBox>

使用应用程序

当您启动应用程序时,DataGrid 应填充Credits

选择一行后,RowDetails将展开。

您可以在RowDetails区域中编辑当前行,并测试触发超链接

DataGrid 下方的按钮是:

使用删除组,您可以获得正常 DataGrid 装备。

按组/状态分组可恢复分组装备。

添加新行保存积分按其名称所示执行。

可以同时使用文本搜索框过滤掉选中的项目(用于选中/未选中的行)。

可以使用上下文菜单进行复制粘贴

上下文菜单

DataGrid上下文菜单提供了一些额外的命令/功能,例如切换列可见性撤销/重做,用于在当前行中进行的编辑。

RowDetails上的RichTextBox有自己的上下文菜单,该菜单在右键单击时显示。

RichTextBoxFormatBar

以下文字摘自RichTextBoxFormatBar · xceedsoftware/wpftoolkit Wiki ·GitHub的

“RichTextBoxFormatBar是一个上下文格式工具栏,它模仿了Microsoft Office 2010格式设置栏的行为。可以使用RichTextBoxFormatBarManager将其附加到任何Richtextbox控件。您甚至可以创建自己的格式条并改用它,但仍然具有RichTextboxFormatBarManager提供的所有功能。

"RichTextBoxFormatBar是一个上下文文本格式设置工具栏,它将对RichTextBox控件的选定文本应用文本转换。当用户处于选择过程中时,RichTextBoxFormatBar将在选择后释放鼠标时出现。RichTextBoxFormatBar也将在最后一次单击双击选择时出现。当显示RichTextFormatBar时,您可以单击要应用于所选文本的任意数量的文本转换。"

致谢/参考资料

https://www.codeproject.com/Tips/5381772/WPF-DataGrid-with-RichText-RowDetails-Grouping-Fil

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值