1.需求分析
树形下拉的功能是ComboBox和TreeView的功能结合起来,再结合数据模板来实现这一功能。
2.代码实现
1.创建UserControl集成TreeView控件
`
public class TreeComboBox : TreeView
{
private bool _isPushTextChangedEvent = true;
private Button ClearButton;
private string _fixedPlaceholderText;
private TextBox ComboBoxText;private object? _selectItem;public TreeComboBox()
{TemplateApplied += (sender, args) =>{ClearButton = args.NameScope.Find<Button>("ClearButton");ComboBoxText = args.NameScope.Find<TextBox>("ComboBoxText");ComboBoxText.TextChanged += (sender, args) => { OwenTextChanged?.Invoke(sender, args); };};PlaceholderTextProperty.Changed.Subscribe(new AnonymousObserver<AvaloniaPropertyChangedEventArgs<string>>((s) =>{if (s.Sender != this) return;if (!string.IsNullOrWhiteSpace(_fixedPlaceholderText)) return;_fixedPlaceholderText = s.NewValue.Value;})!);IsDropDownOpenProperty.Changed.Subscribe(new AnonymousObserver<AvaloniaPropertyChangedEventArgs<bool>>((s) =>{if (s.Sender != this) return;if (s.NewValue == false && SelectedItem != null){SetDisplay(SelectedItem);PlaceholderText = _fixedPlaceholderText;_isPushTextChangedEvent = true;}})!);SelectedItemProperty.Changed.Subscribe(new AnonymousObserver<AvaloniaPropertyChangedEventArgs<object>>((s) =>{if (s.Sender != this) return;if (s.NewValue == null){SetCurrentValue<string>(SelectTextProperty, string.Empty);}}));SelectionChanged += (sender, args) =>{_isPushTextChangedEvent = false;if (args.AddedItems.Count <= 0){this.SetCurrentValue<string>(SelectTextProperty, string.Empty);ComboBoxText?.Focus();return;}if (string.IsNullOrEmpty(DisplayMember)) return;var item = args.AddedItems[0];if (!string.IsNullOrEmpty(LeafMember)){var type = item.GetType();var property = type.GetProperty(LeafMember);if (property != null){int.TryParse(property.GetValue(item).ToString(), out var leaf);if (leaf == 0){SelectedItem = _selectItem;args.Handled = true;return;}_selectItem = SelectedItem;}}SetDisplay(item);};
}private void SetDisplay(object item)
{var type = item.GetType();var property = type.GetProperty(DisplayMember);this.SetCurrentValue<string>(SelectTextProperty, property.GetValue(item).ToString());ClearButton?.Focus();
}public EventHandler<TextChangedEventArgs>? OwenTextChanged { get; set; }public static readonly StyledProperty<string> SelectTextProperty = AvaloniaProperty.Register<TreeComboBox, string>("SelectText");public string SelectText
{get => GetValue(SelectTextProperty);set => SetValue(SelectTextProperty, value);
}public static readonly StyledProperty<string> DisplayMemberProperty =AvaloniaProperty.Register<TreeComboBox, string>("DisplayMember");/// <summary>
/// 显示的字段
/// </summary>
public string DisplayMember
{get => GetValue(DisplayMemberProperty);set => SetValue(DisplayMemberProperty, value);
}public static readonly StyledProperty<string> LeafMemberProperty = AvaloniaProperty.Register<TreeComboBox, string>("LeafMember");/// <summary>
/// 是否过滤不能选中的节点,需要过滤节点的字段
/// </summary>
public string LeafMember
{get => GetValue(LeafMemberProperty);set => SetValue(LeafMemberProperty, value);
}/// <summary>
/// Defines the <see cref="P:Avalonia.Controls.ComboBox.PlaceholderText" /> property.
/// </summary>
public static readonly StyledProperty<string?> PlaceholderTextProperty =AvaloniaProperty.Register<TreeComboBox, string>(nameof(PlaceholderText));/// <summary>Gets or sets the PlaceHolder text.</summary>
public string? PlaceholderText
{get => this.GetValue<string>(TreeComboBox.PlaceholderTextProperty);set => this.SetValue<string>(TreeComboBox.PlaceholderTextProperty, value);
}/// <summary>
/// Defines the <see cref="P:Avalonia.Controls.ComboBox.IsDropDownOpen" /> property.
/// </summary>
public static readonly StyledProperty<bool> IsDropDownOpenProperty =AvaloniaProperty.Register<TreeComboBox, bool>(nameof(IsDropDownOpen));public bool IsDropDownOpen
{get => this.GetValue<bool>(TreeComboBox.IsDropDownOpenProperty);set { this.SetValue<bool>(TreeComboBox.IsDropDownOpenProperty, value); }
}public void Clear()
{this.SetCurrentValue<string>(SelectTextProperty, string.Empty);SelectedItems.Clear();_selectItem = null;PlaceholderText = _fixedPlaceholderText;if (ComboBoxText != null){ComboBoxText.Text = "";ComboBoxText.Focus();}
}private Popup? _popup;protected override void OnPointerPressed(PointerPressedEventArgs e)
{base.OnPointerPressed(e);if (!e.Handled && e.Source is Visual source){Popup popup = this._popup;if ((popup != null ? (popup.IsInsidePopup(source) ? 1 : 0) : 0) != 0){e.Handled = true;return;}}this.PseudoClasses.Set(":pressed", true);
}protected override void OnPointerReleased(PointerReleasedEventArgs e)
{if (SelectedItem != null){PlaceholderText = SelectText;SelectText = string.Empty;ComboBoxText.Focus();}if (!e.Handled && e.Source is Visual source){Popup popup = this._popup;if ((popup != null ? (popup.IsInsidePopup(source) ? 1 : 0) : 0) != 0){if (this.UpdateSelectionFromEventSource(e.Source)){this._popup?.Close();e.Handled = true;}}else{this.SetCurrentValue<bool>(TreeComboBox.IsDropDownOpenProperty, !this.IsDropDownOpen);e.Handled = true;}}this.PseudoClasses.Set(":pressed", false);base.OnPointerReleased(e);
}
}
`
2.将ComboBox模板代码,和必要属性集成到新建的UserControl中
<ControlTheme x:Key="{x:Type control:TreeComboBox}" TargetType="control:TreeComboBox"><Setter Property="Template"><ControlTemplate TargetType="Calendar"><Grid ColumnDefinitions="*,12,32"><Borderx:Name="Background"Grid.Column="0"Grid.ColumnSpan="3"MinWidth="{DynamicResource ComboBoxThemeMinWidth}"MinHeight="{DynamicResource ComboBoxDefaultHeight}"Background="{TemplateBinding Background}"BorderBrush="{TemplateBinding BorderBrush}"BorderThickness="{TemplateBinding BorderThickness}"CornerRadius="{TemplateBinding CornerRadius}" /><StackPanel Grid.Column="0" ><TextBoxx:Name="ComboBoxText"Margin="{TemplateBinding Padding}"Width="{Binding $parent.Bounds.Width}"HorizontalAlignment="Left"VerticalAlignment="Center"Watermark="{Binding $parent[control:TreeComboBox].PlaceholderText}"Foreground="{TemplateBinding Foreground}"IsVisible="{Binding $parent[control:TreeComboBox].SelectText,Converter={x:Static StringConverters.IsNullOrEmpty}}"></TextBox><TextBoxWidth="{Binding $parent.Bounds.Width}"Margin="{TemplateBinding Padding}"HorizontalAlignment="Left"VerticalAlignment="Center"Foreground="{TemplateBinding Foreground}"IsVisible="{Binding $parent[control:TreeComboBox].SelectText,Converter={x:Static StringConverters.IsNotNullOrEmpty}}"Text="{Binding $parent[control:TreeComboBox].SelectText}"></TextBox></StackPanel><Borderx:Name="DropDownOverlay"Grid.Column="1"Width="30"Margin="0,1,1,1"HorizontalAlignment="Right"Background="Transparent"IsVisible="False" /><Button Grid.Column="1" Width="12"Name="ClearButton"Tag="{TemplateBinding Name}"DockPanel.Dock="Right"Background="Transparent"Command="{Binding $parent[control:TreeComboBox].Clear}"IsVisible="{Binding $parent[control:TreeComboBox].SelectedItem,Converter={x:Static ObjectConverters.IsNotNull}}"Height="12"><Button.Template><ControlTemplate><Border><PathIcon Data="{StaticResource TextBoxClearButtonData}"Foreground="{DynamicResource ComboBoxIconDefaultForeground}"Width="12"Height="12"></PathIcon></Border></ControlTemplate></Button.Template><Button.Styles><Style Selector="Button:pointerover"><Setter Property="Background" Value="Transparent"></Setter></Style></Button.Styles></Button><PathIconx:Name="DropDownGlyph"Grid.Column="2"Width="12"Height="12"Margin="0,0,10,0"HorizontalAlignment="Right"VerticalAlignment="Center"Data="{DynamicResource ComboBoxIcon}"Foreground="{DynamicResource ComboBoxIconDefaultForeground}"IsHitTestVisible="False"UseLayoutRounding="False" /><PopupName="PART_Popup"Grid.Column="0"MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"MaxHeight="300"ClipToBounds="False"InheritsTransform="True"IsLightDismissEnabled="True"PlacementTarget="Background"IsOpen="{Binding $parent[control:TreeComboBox].IsDropDownOpen,Mode=TwoWay}"WindowManagerAddShadowHint="False"><Borderx:Name="PopupBorder"Margin="0,4"HorizontalAlignment="Stretch"Background="{DynamicResource ComboBoxPopupBackground}"BorderBrush="{DynamicResource ComboBoxPopupBorderBrush}"BorderThickness="{DynamicResource ComboBoxPopupBorderThickness}"BoxShadow="{DynamicResource ComboBoxPopupBoxShadow}"ClipToBounds="True"CornerRadius="6"><ScrollViewerHorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"><ItemsPresenterName="PART_ItemsPresenter"Margin="{DynamicResource ComboBoxDropdownContentMargin}" /></ScrollViewer></Border></Popup></Grid></ControlTemplate></Setter></ControlTheme>
3.运行效果
4.代码地址
https://github.com/klousCan/Avalonia.TreeComboBox.git