DevExpress WPF 自定义 SearchPanel

366 阅读2分钟

使用场景

在使用GridControl时,经常配套使用SearchPanel,有时需要自定义该区域,以显示期望的自定义内容。下面的例子自定义该区域,以显示查询过滤条目的数量。

样例代码

自定义 SearchPanelContentTemplate

  <Style x:Key="{dxgt:TableViewThemeKey ResourceKey=SearchPanelContentTemplate, IsThemeIndependent=True}" TargetType="{x:Type ContentControl}">
            <Setter Property="dx:FocusHelper2.Focusable" Value="False"/>
            <Setter Property="IsTabStop" Value="False"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Grid Background="{DynamicResource {dxgt:TableViewThemeKey ResourceKey=SearchPanelBackground}}">
                            <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                                <dxg:GridSearchControl x:Name="PART_SearchControl" View="{Binding Path=(dxg:GridControl.CurrentView), RelativeSource={RelativeSource TemplatedParent}}" 
                                                   IsEditorTabStop="False" IsTabStop="False" 
                                                   HorizontalAlignment="{Binding Path=(dxg:GridControl.CurrentView).SearchPanelHorizontalAlignment, RelativeSource={RelativeSource TemplatedParent}}" 
                                                   SearchTextBoxMinWidth="{DynamicResource {dxgt:TableViewThemeKey ResourceKey=SearchPanelTextBoxMinWidth}}"
                                                   Visibility="{Binding Path=View.ActualShowSearchPanel, RelativeSource={RelativeSource Self}, Converter={dx:BooleanToVisibilityConverter}}"
                                                   Margin="{DynamicResource {dxgt:TableViewThemeKey ResourceKey=SearchPanelWithoutGroupedPanelMargin}}">
                                </dxg:GridSearchControl>
                                <TextBlock Margin="{DynamicResource {dxgt:TableViewThemeKey ResourceKey=SearchPanelWithoutGroupedPanelMargin}}" Grid.Row="1" Text="{Binding Path=(dxg:GridControl.CurrentView).SearchResultsCount, RelativeSource={RelativeSource TemplatedParent},StringFormat=Count search results: {0}}" />
                            </Grid>
                            <Border x:Name="PART_SearchPanelBorderBottom" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource {dxgt:TableViewThemeKey ResourceKey=SearchPanelBorderBottomBrush}}">
                                <Border.Visibility>
                                    <Binding Path="GroupPanelShown" ElementName="PART_SearchControl">
                                        <Binding.Converter>
                                            <dx:BoolToVisibilityInverseConverter/>
                                        </Binding.Converter>
                                    </Binding>
                                </Border.Visibility>
                            </Border>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="GroupPanelShown" SourceName="PART_SearchControl" Value="True">
                                <Setter Property="Margin" Value="{DynamicResource {dxgt:TableViewThemeKey ResourceKey=SearchPanelWithGroupedPanelMargin}}" TargetName="PART_SearchControl"/>
                            </Trigger>
                            <DataTrigger Binding="{Binding Path=(dxg:GridControl.CurrentView).IsCompactMode, RelativeSource={RelativeSource TemplatedParent}}" Value="True">
                                <Setter TargetName="PART_SearchControl" Property="HorizontalAlignment" Value="Stretch" />
                            </DataTrigger>
                            <DataTrigger Binding="{Binding Path=(dxg:GridControl.CurrentView).ActualShowCompactPanel, RelativeSource={RelativeSource TemplatedParent}}" Value="True">
                                <Setter TargetName="PART_SearchPanelBorderBottom" Property="Visibility" Value="Collapsed" />
                            </DataTrigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

MainWindow

<Window x:Class="WpfApp17.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp17"
        xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
        xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
        xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
        xmlns:dxgt="http://schemas.devexpress.com/winfx/2008/xaml/grid/themekeys"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>

    <Window.Resources>
        <Style x:Key="{dxgt:TableViewThemeKey ResourceKey=SearchPanelContentTemplate, IsThemeIndependent=True}" TargetType="{x:Type ContentControl}">
            <Setter Property="dx:FocusHelper2.Focusable" Value="False"/>
            <Setter Property="IsTabStop" Value="False"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Grid Background="{DynamicResource {dxgt:TableViewThemeKey ResourceKey=SearchPanelBackground}}">
                            <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                                <dxg:GridSearchControl x:Name="PART_SearchControl" View="{Binding Path=(dxg:GridControl.CurrentView), RelativeSource={RelativeSource TemplatedParent}}" 
                                                   IsEditorTabStop="False" IsTabStop="False" 
                                                   HorizontalAlignment="{Binding Path=(dxg:GridControl.CurrentView).SearchPanelHorizontalAlignment, RelativeSource={RelativeSource TemplatedParent}}" 
                                                   SearchTextBoxMinWidth="{DynamicResource {dxgt:TableViewThemeKey ResourceKey=SearchPanelTextBoxMinWidth}}"
                                                   Visibility="{Binding Path=View.ActualShowSearchPanel, RelativeSource={RelativeSource Self}, Converter={dx:BooleanToVisibilityConverter}}"
                                                   Margin="{DynamicResource {dxgt:TableViewThemeKey ResourceKey=SearchPanelWithoutGroupedPanelMargin}}">
                                </dxg:GridSearchControl>
                                <TextBlock Margin="{DynamicResource {dxgt:TableViewThemeKey ResourceKey=SearchPanelWithoutGroupedPanelMargin}}" Grid.Row="1" Text="{Binding Path=(dxg:GridControl.CurrentView).SearchResultsCount, RelativeSource={RelativeSource TemplatedParent},StringFormat=Count search results: {0}}" />
                            </Grid>
                            <Border x:Name="PART_SearchPanelBorderBottom" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource {dxgt:TableViewThemeKey ResourceKey=SearchPanelBorderBottomBrush}}">
                                <Border.Visibility>
                                    <Binding Path="GroupPanelShown" ElementName="PART_SearchControl">
                                        <Binding.Converter>
                                            <dx:BoolToVisibilityInverseConverter/>
                                        </Binding.Converter>
                                    </Binding>
                                </Border.Visibility>
                            </Border>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="GroupPanelShown" SourceName="PART_SearchControl" Value="True">
                                <Setter Property="Margin" Value="{DynamicResource {dxgt:TableViewThemeKey ResourceKey=SearchPanelWithGroupedPanelMargin}}" TargetName="PART_SearchControl"/>
                            </Trigger>
                            <DataTrigger Binding="{Binding Path=(dxg:GridControl.CurrentView).IsCompactMode, RelativeSource={RelativeSource TemplatedParent}}" Value="True">
                                <Setter TargetName="PART_SearchControl" Property="HorizontalAlignment" Value="Stretch" />
                            </DataTrigger>
                            <DataTrigger Binding="{Binding Path=(dxg:GridControl.CurrentView).ActualShowCompactPanel, RelativeSource={RelativeSource TemplatedParent}}" Value="True">
                                <Setter TargetName="PART_SearchPanelBorderBottom" Property="Visibility" Value="Collapsed" />
                            </DataTrigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    
    <Grid>
        <dxg:GridControl ItemsSource="{Binding Items}">
            <dxg:GridControl.Columns>
                <dxg:GridColumn FieldName="Id" />
                <dxg:GridColumn FieldName="Name" />
                <dxg:GridColumn FieldName="Group" />
                <dxg:GridColumn FieldName="IsSet" />
                <dxg:GridColumn FieldName="Time" />
            </dxg:GridControl.Columns>
            <dxg:GridControl.View>
                <local:TreeListViewEx ShowSearchPanelMode="Always" SearchPanelAllowFilter="True" SearchString="Name_1" 
                                      TreeDerivationMode="Selfreference"
                                      KeyFieldName="Id"
                                      ParentFieldName="ParentId"
                                      ScrollBarAnnotationMode="SearchResult"
                                      AutoExpandAllNodes="True"/>
            </dxg:GridControl.View>
        </dxg:GridControl>
    </Grid>
</Window>

MainWindow.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using DevExpress.Xpf.Grid;

namespace WpfApp17 {
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }
    }

    public class TreeListViewEx : TreeListView {
        public int SearchResultsCount {
            get { return (int)GetValue(SearchResultsCountProperty); }
            set { SetValue(SearchResultsCountProperty, value); }
        }
        public static readonly DependencyProperty SearchResultsCountProperty =
            DependencyProperty.Register("SearchResultsCount", typeof(int), typeof(TreeListViewEx), new PropertyMetadata(0));

        List<int> searchResults = new List<int>();
        protected override void UpdateFilterGrid() {
            base.UpdateFilterGrid();
            searchResults.Clear();
            var fitPredicate = CreateFilterFitPredicate();
            if(fitPredicate == null) {
                SearchResultsCount = 0;
                return;
            }
            for(int i = 0; i < DataProviderBase.DataRowCount; i++)
                if(fitPredicate(i))
                    searchResults.Add(i);
            SearchResultsCount = searchResults.Count;
        }    
    }
}

ViewModel.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfApp17 {
    public class ViewModel {
        ObservableCollection<Item> _items;
        public IEnumerable<Item> Items { get { return _items; } }

        public ViewModel() {
            _items = new ObservableCollection<Item>();
            for(int i = 0; i < 100; i++)
                _items.Add(new Item(i));
        }
    }

    public class Item : INotifyPropertyChanged {

        public Item() { }

        public Item(int id) : base() {
            Id = id + 1;
            ParentId = Id < 5 ? -1 : Id / 5;
            Name = "Name_" + id;
            Group = "Group_" + id % 10;
            IsSet = id % 2 == 0;
            Time = DateTime.Now.AddDays(-id);
        }

        int _id;
        public int Id { get { return _id; } set { _id = value; OnPropertyChanged("Id"); } }

        int _parentId;
        public int ParentId { get { return _parentId; } set { _parentId = value; OnPropertyChanged("ParentId"); } }

        string _name;
        public string Name { get { return _name; } set { _name = value; OnPropertyChanged("Name"); } }

        string _group;
        public string Group { get { return _group; } set { _group = value; OnPropertyChanged("Group"); } }

        bool _isSet;
        public bool IsSet { get { return _isSet; } set { _isSet = value; OnPropertyChanged("IsSet"); } }

        DateTime _time;
        public DateTime Time { get { return _time; } set { _time = value; OnPropertyChanged("Time"); } }


        void OnPropertyChanged(string propertyName) {
            if(PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

运行效果

image.png