• Wpf实现TreeSelect


    实现思路:

    1.继承ComboBox

    2.重写ComboBox的模板,把列表控件替换成树形控件

    3.重写SelectedItem, SelectedValue,DisplayMemberPath,SelectedValuePath

    效果截图:

    XAML代码

        <!--TreeSelect普通样式-->
        <Style x:Key="DefaultTreeSelectStyle" TargetType="{x:Type controls:TreeSelect}">
            <Setter Property="Foreground" Value="{DynamicResource TextBrush}" />
            <Setter Property="Background" Value="{DynamicResource ControlBackgroundBrush}" />
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
            <Setter Property="VerticalContentAlignment" Value="Center" />
            <Setter Property="BorderThickness" Value="1" />
            <Setter Property="BorderBrush" Value="{DynamicResource TextBoxBorderBrush}" />
            <Setter Property="controls:ControlAttachProperty.FocusBackground" Value="Transparent" />
            <Setter Property="controls:ControlAttachProperty.FocusBorderBrush" Value="{DynamicResource TextBoxFocusBorderBrush}" />
            <Setter Property="controls:ControlAttachProperty.MouseOverBorderBrush" Value="{DynamicResource TextBoxMouseOverBorderBrush}" />
            <Setter Property="controls:ControlAttachProperty.PopupBackground" Value="{DynamicResource WhiteBrush}" />
            <Setter Property="FontFamily" Value="{DynamicResource ContentFontFamily}" />
            <Setter Property="FontSize" Value="{DynamicResource ContentFontSize}" />
            <Setter Property="SnapsToDevicePixels" Value="True" />
            <Setter Property="MaxDropDownHeight" Value="200" />
            <Setter Property="ScrollViewer.CanContentScroll" Value="False" />
            <Setter Property="MinHeight" Value="22" />
            <Setter Property="Padding" Value="2" />
            <Setter Property="ItemTemplate">
                <Setter.Value>
                    <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                        <StackPanel MinHeight="22" Orientation="Horizontal" Background="Transparent" HorizontalAlignment="Left" >
                            <TextBlock Text="{TemplateBinding Content}" VerticalAlignment="Center" HorizontalAlignment="Left"/>
                        </StackPanel>
                    </HierarchicalDataTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type controls:TreeSelect}">
                        <Grid x:Name="PART_Root">
                            <Border x:Name="Bg" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                    BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
                                    Background="{TemplateBinding Background}" />
                            <Grid x:Name="PART_InnerGrid" Margin="0">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="*" />
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="21" />
                                </Grid.ColumnDefinitions>
                                <!--Label区域-->
                                <ContentControl x:Name="Label" Template="{TemplateBinding controls:ControlAttachProperty.LabelTemplate}" IsTabStop="False" IsHitTestVisible="False"
                                                Content="{TemplateBinding controls:ControlAttachProperty.Label}" Margin="1,1,0,1"/>
                                <!--附加内容区域-->
                                <Border x:Name="PART_AttachContent" Panel.ZIndex="2" Grid.Column="2" VerticalAlignment="Center" HorizontalAlignment="Center" >
                                    <ContentControl VerticalAlignment="Center" VerticalContentAlignment="Center" Template="{TemplateBinding controls:ControlAttachProperty.AttachContent}" />
                                </Border>
                                <!--下拉按钮-->
                                <ToggleButton x:Name="PART_DropDownToggle" Panel.ZIndex="1" IsTabStop="False" Style="{StaticResource ComboToggleButton}" 
                                             IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
                                             Grid.Column="1"  Grid.ColumnSpan="3" IsEnabled="{Binding Path=IsReadOnly,RelativeSource={RelativeSource TemplatedParent},
                                                Converter={x:Static controls:XConverter.TrueToFalseConverter},Mode=OneWay}" Margin="2 1 10 1"
                                              Background="{TemplateBinding controls:ControlAttachProperty.FocusBackground}"/>
                                <!--水印-->
                                <Border Grid.Column="1">
                                    <TextBlock x:Name="Message"  Padding="0" Visibility="Collapsed" Text="{TemplateBinding controls:ControlAttachProperty.Watermark}" 
                                           Foreground="{TemplateBinding Foreground}" IsHitTestVisible="False" Opacity="0.6" HorizontalAlignment="Left" TextAlignment="Center" 
                                           VerticalAlignment="Center" Margin="5,2,5,2" />
                                </Border>
                                <!--内容区-->
                                <Grid Grid.Column="1"  Margin="2 0 0 0">
                                    <!--文本编辑-->
                                    <TextBox  x:Name="PART_EditableTextBox" Style="{StaticResource EditableTextBoxStyle}" FontSize="{TemplateBinding FontSize}"
                                             HorizontalAlignment="Stretch" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" IsHitTestVisible="True"
                                             HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                             IsReadOnly="{TemplateBinding IsReadOnly}" FontFamily="{TemplateBinding FontFamily}" Foreground="{TemplateBinding Foreground}"
                                             Text="{Binding Path=Text,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"  />
                                </Grid>
                                <!--弹出多选列表-->
                                <Popup x:Name="PART_Popup" AllowsTransparency="True" Focusable="False" StaysOpen="False"
                                   IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}"
                                   PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
                                    <Grid Width="{Binding ActualWidth, RelativeSource={RelativeSource TemplatedParent}}" MaxHeight="{Binding MaxDropDownHeight, RelativeSource={RelativeSource TemplatedParent}}">
                                        <Border x:Name="PopupBorder" BorderThickness="{TemplateBinding BorderThickness}" HorizontalAlignment="Stretch"
                                            Height="Auto" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding controls:ControlAttachProperty.PopupBackground}"/>
                                        <controls:ExtendedTreeView x:Name="PART_TreeView" Margin="2" ItemsSource="{Binding ItemsSource,RelativeSource={RelativeSource TemplatedParent}}"
                                                  MaxHeight="{TemplateBinding MaxDropDownHeight}"  ItemTemplate="{TemplateBinding ItemTemplate}"
                                                  Style="{StaticResource DefaultMetroTreeView}">
                                        </controls:ExtendedTreeView>
                                    </Grid>
                                </Popup>
                            </Grid>
                        </Grid>
                        <!--触发器-->
                        <ControlTemplate.Triggers>
                            <!--1.显示水印-->
                            <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="">
                                <Setter TargetName="Message" Property="Visibility" Value="Visible" />
                            </DataTrigger>
                            <!--编辑模式-->
                            <Trigger Property="IsEditable" Value="True">
                                <Setter TargetName="PART_DropDownToggle" Property="Grid.Column" Value="3" />
                                <Setter TargetName="PART_DropDownToggle" Property="Grid.ColumnSpan" Value="1" />
                                <Setter TargetName="PART_DropDownToggle" Property="Background" Value="Transparent" />
                                <Setter Property="IsTabStop" Value="false" />
                                <Setter TargetName="PART_DropDownToggle" Property="Focusable" Value="False" />
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="BorderBrush" Value="{Binding Path=(controls:ControlAttachProperty.MouseOverBorderBrush),RelativeSource={RelativeSource Self}}"/>
                            </Trigger>
                            <Trigger Property="IsFocused" Value="True">
                                <Setter  Property="BorderBrush" Value="{Binding Path=(controls:ControlAttachProperty.FocusBorderBrush),RelativeSource={RelativeSource Self}}"/>
                            </Trigger>
                            <Trigger Property="IsKeyboardFocusWithin" Value="True">
                                <Setter  Property="BorderBrush" Value="{Binding Path=(controls:ControlAttachProperty.FocusBorderBrush),RelativeSource={RelativeSource Self}}"/>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter TargetName="PART_Root" Property="Opacity" Value="{DynamicResource DisableOpacity}"></Setter>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    

     cs代码

        /// <summary>
        /// TreeSelect.xaml 的交互逻辑
        /// </summary>
        [TemplatePart(Name = "PART_TreeView", Type = typeof(TreeView))]
        public partial class TreeSelect : ComboBox
        {
            public bool IsMulti
            {
                get { return (bool)GetValue(IsMultiProperty); }
                set { SetValue(IsMultiProperty, value); }
            }
    
            public static readonly DependencyProperty IsMultiProperty =
                DependencyProperty.Register("IsMulti", typeof(bool), typeof(TreeSelect), new PropertyMetadata(false));
    
            public new string DisplayMemberPath
            {
                get { return (string)GetValue(DisplayMemberPathProperty); }
                set { SetValue(DisplayMemberPathProperty, value); }
            }
    
            public new static readonly DependencyProperty DisplayMemberPathProperty =
                DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(TreeSelect));
    
            public new string SelectedValuePath
            {
                get { return (string)GetValue(SelectedValuePathProperty); }
                set { SetValue(SelectedValuePathProperty, value);  }
            }
    
            public new static readonly DependencyProperty SelectedValuePathProperty =
                DependencyProperty.Register("SelectedValuePath", typeof(string), typeof(TreeSelect));
    
            /// <summary>
            /// Selected item of the TreeView
            /// </summary>
            public new object SelectedItem
            {
                get { return (object)GetValue(SelectedItemProperty); }
                set { SetValue(SelectedItemProperty, value); }
            }
    
            public new static readonly DependencyProperty SelectedItemProperty =
                DependencyProperty.Register("SelectedItem", typeof(object), typeof(TreeSelect), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedItemChanged)));
    
            private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
            {
                ((TreeSelect)sender).UpdateSelectedItem();
    
                
            }
    
            public new object SelectedValue
            {
                get { return (object)GetValue(SelectedValueProperty); }
                set { SetValue(SelectedValueProperty, value); }
            }
    
            public new static readonly DependencyProperty SelectedValueProperty =
                DependencyProperty.Register("SelectedValue", typeof(object), typeof(TreeSelect), new PropertyMetadata(null));
    
    
            public new string Text
            {
                get { return (string)GetValue(TextProperty); }
                set { SetValue(TextProperty, value); }
            }
    
            public static readonly DependencyProperty TextProperty =
                DependencyProperty.Register("Text", typeof(string), typeof(TreeSelect));
    
            /// <summary>
            /// Gets or sets text separator.
            /// </summary>
            public string TextSeparator
            {
                get { return (string)GetValue(TextSeparatorProperty); }
                set { SetValue(TextSeparatorProperty, value); }
            }
    
            public static readonly DependencyProperty TextSeparatorProperty =
                DependencyProperty.Register("TextSeparator", typeof(string), typeof(TreeSelect), new PropertyMetadata(","));
    
            /// <summary>
            /// Gets or sets max text length.
            /// </summary>
            public int? MaxTextLength
            {
                get { return (int?)GetValue(MaxTextLengthProperty); }
                set { SetValue(MaxTextLengthProperty, value); }
            }
    
            public static readonly DependencyProperty MaxTextLengthProperty =
                DependencyProperty.Register("MaxTextLength", typeof(int?), typeof(TreeSelect));
    
            /// <summary>
            /// Gets or sets text filler when text length exceeded.
            /// </summary>
            public string ExceededTextFiller
            {
                get { return (string)GetValue(ExceededTextFillerProperty); }
                set { SetValue(ExceededTextFillerProperty, value); }
            }
    
            public static readonly DependencyProperty ExceededTextFillerProperty =
                DependencyProperty.Register("ExceededTextFiller", typeof(string), typeof(TreeSelect), new PropertyMetadata("..."));
    
            static TreeSelect()
            {
                DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeSelect), new FrameworkPropertyMetadata(typeof(TreeSelect)));
            }
    
            public TreeSelect()
            {
                Loaded -= TreeSelect_Loaded;
                Loaded += TreeSelect_Loaded;
                SizeChanged -= TreeSelect_SizeChanged;
                SizeChanged += TreeSelect_SizeChanged;
            }
    
            private ExtendedTreeView _treeView;
    
            public override void OnApplyTemplate()
            {
                base.OnApplyTemplate();
                this._treeView = Template.FindName("PART_TreeView", this) as ExtendedTreeView;
                if (this._treeView != null)
                {
                    //this._treeView.SelectedItemChanged += _TreeView_SelectedItemChanged;
                    _treeView.OnHierarchyMouseUp += new MouseEventHandler(OnTreeViewHierarchyMouseUp);
                    _treeView.AddHandler(System.Windows.Controls.TreeViewItem.SelectedEvent, new System.Windows.RoutedEventHandler(treeview_Selected));
                }
            }
    
            //protected override void OnDropDownClosed(EventArgs e)
            //{
            //    base.OnDropDownClosed(e);
            //    this.SelectedItem = _treeView.SelectedItem;
            //    this.UpdateText();
            //}
    
            //protected override void OnDropDownOpened(EventArgs e)
            //{
            //    base.OnDropDownOpened(e);
            //    this.UpdateText();
            //}
    
            /// <summary>
            /// Handles clicks on any item in the tree view
            /// </summary>
            private void OnTreeViewHierarchyMouseUp(object sender, MouseEventArgs e)
            {
                //This line isn't obligatory because it is executed in the OnDropDownClosed method, but be it so
                this.SelectedItem = _treeView.SelectedItem;
                this.UpdateText(); 
                base.SelectedItem = this.SelectedItem;
                this.IsDropDownOpen = false;
            }
    
            private void treeview_Selected(object sender, RoutedEventArgs e)
            {
                TreeViewItem item = (e.OriginalSource as TreeViewItem);
                if (item != null)
                {
                    item.BringIntoView();
                }
            }
    
            protected override bool IsItemItsOwnContainerOverride(object item)
            {
                if (item is ComboBoxItem)
                    return true;
                else
                    return false;
            }
    
            protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
            {
                var uie = element as FrameworkElement;
    
                if (!(item is ComboBoxItem))
                {
                    var textBinding = new Binding(DisplayMemberPath);
                    textBinding.Source = item;
                    uie.SetBinding(ContentPresenter.ContentProperty, textBinding);
                }
    
                base.PrepareContainerForItemOverride(element, item);
            }
    
            private void TreeSelect_SizeChanged(object sender, SizeChangedEventArgs e)
            {
                if (!IsLoaded)
                    return;
    
                UpdateText();
            }
            private void TreeSelect_Loaded(object sender, RoutedEventArgs e)
            {
                UpdateText();
            }
    
            private void UpdateSelectedItem()
            {
                UpdateText();
                base.SelectedItem = this.SelectedItem;
    
                if (this.SelectedItem == null || string.IsNullOrEmpty(this.SelectedValuePath))
                {
                    SelectedValue = null;                
                }
                else
                {
                    SelectedValue = this.SelectedItem.GetType().GetProperty(SelectedValuePath).GetValue(this.SelectedItem, null);
                }
            }
    
            private void _TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
            {
                if (!IsLoaded)
                    return;           
    
                UpdateText();
                base.SelectedItem = this.SelectedItem;
            }
    
            private void UpdateText()
            {
                if (IsMulti == false)
                {
                    Text = GenerateText(SelectedItem);
                }
                else
                {
                    Text = GenerateText();
                }
            }
    
            public string GenerateText(object selectedItem)
            {
                var text = "";
                if (selectedItem == null)
                {
                    text = "";
                }
                else if (selectedItem is ComboBoxItem)
                {
                    var msi = selectedItem as ComboBoxItem;
                    text += msi.Content.ToString();
                }
                else
                {
                    if (!string.IsNullOrEmpty(DisplayMemberPath) && selectedItem.GetType().GetProperty(DisplayMemberPath) != null)
                        text += selectedItem.GetType().GetProperty(DisplayMemberPath).GetValue(selectedItem, null).ToString();
                    else
                        text += selectedItem.ToString();
    
                    if (selectedItem.GetType().GetProperty("IsSelected") != null)
                    {
                        selectedItem.GetType().GetProperty("IsSelected").SetValue(selectedItem, true);
                    }
                }
    
                return text;
            }
    
            public string GenerateText()
            {
                var text = "";
                var isFirst = true;
    
                foreach (var item in Items)
                {
                    if (!isFirst)
                        text += TextSeparator;
                    else
                        isFirst = false;
    
                    if (item is ComboBoxItem)
                    {
                        var msi = item as ComboBoxItem;
                        text += msi.Content.ToString();
                    }
                    else
                    {
                        if (item.GetType().GetProperty("IsSelected") != null && item.GetType().GetProperty("IsSelected").GetValue(item, null).ToString() == "true")
                        {
                            if (!string.IsNullOrEmpty(DisplayMemberPath) && item.GetType().GetProperty(DisplayMemberPath) != null)
                                text += item.GetType().GetProperty(DisplayMemberPath).GetValue(item, null).ToString();
                            else
                                text += item.ToString();
                        }
                    }
    
                    if (MaxTextLength == null)
                    {
                        if (!ValidateStringWidth(text + ExceededTextFiller))
                        {
                            if (text.Length == 0)
                                return null;
                            text = text.Remove(text.Length - 1);
                            while (!ValidateStringWidth(text + ExceededTextFiller))
                            {
                                if (text.Length == 0)
                                    return null;
                                text = text.Remove(text.Length - 1);
                            }
                            return text + ExceededTextFiller;
                        }
                    }
                    else if (text.Length >= MaxTextLength)
                    {
                        return text.Cut((int)MaxTextLength, ExceededTextFiller);
                    }
                }
                return text;
            }
    
            private bool ValidateStringWidth(string text)
            {
                var size = MeasureString(text);
                if (size.Width > (ActualWidth - Padding.Left - Padding.Right - 30))
                    return false;
                else
                    return true;
    
            }
    
            private Size MeasureString(string candidate)
            {
                var formattedText = new FormattedText(candidate, System.Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), FontSize, Brushes.Black, new NumberSubstitution(), TextFormattingMode.Display);
    
                return new Size(formattedText.Width, formattedText.Height);
            }
        }

    使用:

    <util:TreeSelect
      Width="200"
      ItemsSource="{Binding Data}"
      SelectedItem="{Binding SelectedData}"
      ItemTemplate="{x:Null}"
      DisplayMemberPath="Name" xmlns:util="https://astudio.github.io/utilcontrol">
      <util:TreeSelect.Resources>
        <HierarchicalDataTemplate
          DataType="{x:Type viewmodel:Person}"
          ItemsSource="{Binding Path=Children}">
          <StackPanel
            Orientation="Horizontal">
            <Path
              x:Name="IconPath"
              Width="18"
              Height="18"
              Stretch="Fill"
              Fill="Black"
              Data="F1 M 24.0033,56.0078L 24.0033,38.0053L 22.0031,40.0056L 19.0027,35.0049L 38.0053,20.0028L 45.0063,25.5299L 45.0063,21.753L 49.0068,21.0029L 49.0068,28.6882L 57.008,35.0049L 54.0075,40.0056L 52.0073,38.0053L 52.0073,56.0078L 24.0033,56.0078 Z M 38.0053,26.9204L 27.0038,36.005L 27.0038,53.0074L 33.0046,53.0074L 33.0046,42.006L 43.006,42.006L 43.006,53.0074L 49.0068,53.0074L 49.0068,36.005L 38.0053,26.9204 Z " />
            <Grid
              Margin="2,0,2,0">
              <TextBlock
                x:Name="txtName"
                Text="{Binding Name, Mode=TwoWay}"
                Width="Auto" />
            </Grid>
          </StackPanel>
        </HierarchicalDataTemplate>
      </util:TreeSelect.Resources>
    </util:TreeSelect>

     完成,这个还支持多选的,没有完全完成,待续。

    作者:竹天笑
    互相学习,提高自己。
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    正确添加Google Adsense
    微软开发主管临别诤言
    DZ论坛重建管理员
    Cook book 第4天 第6章 层、自定义组件
    Cook Book 第二天 运行环境识别修改
    flex cookbook 学习第一天 基本知识
    C#:String类型中的CharAt
    对我学C#时的一次小回忆[一:语法篇]
    分享一段C#反射代码[Type是反射的入口][查看类型信息][动态生成对象]
    C#反射:让私有成员无所遁形
  • 原文地址:https://www.cnblogs.com/akwkevin/p/14199966.html
Copyright © 2020-2023  润新知