62
demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml
Normal file
62
demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<UserControl
|
||||||
|
x:Class="Ursa.Demo.Pages.TreeComboBoxDemo"
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
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:u="https://irihi.tech/ursa"
|
||||||
|
xmlns:vm="clr-namespace:Ursa.Demo.ViewModels"
|
||||||
|
d:DesignHeight="450"
|
||||||
|
d:DesignWidth="800"
|
||||||
|
x:CompileBindings="True"
|
||||||
|
x:DataType="vm:TreeComboBoxDemoViewModel"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<StackPanel>
|
||||||
|
<u:TreeComboBox Width="300" HorizontalAlignment="Left">
|
||||||
|
<u:TreeComboBoxItem Header="Hello">
|
||||||
|
<u:TreeComboBoxItem Header="Hello World">
|
||||||
|
<u:TreeComboBoxItem Header="Hello World 1" />
|
||||||
|
<u:TreeComboBoxItem Header="Hello World 2" />
|
||||||
|
<u:TreeComboBoxItem Header="Hello World 3" />
|
||||||
|
</u:TreeComboBoxItem>
|
||||||
|
<u:TreeComboBoxItem Header="Hello Avalonia" />
|
||||||
|
<u:TreeComboBoxItem Header="Hello Another" />
|
||||||
|
</u:TreeComboBoxItem>
|
||||||
|
<u:TreeComboBoxItem Header="World">
|
||||||
|
<u:TreeComboBoxItem Header="World Hello" />
|
||||||
|
<u:TreeComboBoxItem Header="World Avalonia" />
|
||||||
|
<u:TreeComboBoxItem Header="World Another" />
|
||||||
|
</u:TreeComboBoxItem>
|
||||||
|
</u:TreeComboBox>
|
||||||
|
<u:TreeComboBox
|
||||||
|
Width="300"
|
||||||
|
Watermark="Please select an item. "
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
SelectedItem="{Binding SelectedItem}"
|
||||||
|
ItemsSource="{Binding Items}">
|
||||||
|
<u:TreeComboBox.ItemTemplate>
|
||||||
|
<TreeDataTemplate ItemsSource="{Binding Children}">
|
||||||
|
<TextBlock Text="{Binding ItemName}" />
|
||||||
|
</TreeDataTemplate>
|
||||||
|
</u:TreeComboBox.ItemTemplate>
|
||||||
|
</u:TreeComboBox>
|
||||||
|
|
||||||
|
<u:TreeComboBox
|
||||||
|
Classes="clearButton"
|
||||||
|
Width="300"
|
||||||
|
Watermark="Please select an item. "
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
SelectedItem="{Binding SelectedItem}"
|
||||||
|
InnerRightContent="Right"
|
||||||
|
InnerLeftContent="Left"
|
||||||
|
PopupInnerTopContent="Top"
|
||||||
|
PopupInnerBottomContent="Bottom"
|
||||||
|
ItemsSource="{Binding Items}">
|
||||||
|
<u:TreeComboBox.ItemTemplate>
|
||||||
|
<TreeDataTemplate ItemsSource="{Binding Children}">
|
||||||
|
<TextBlock Text="{Binding ItemName}" />
|
||||||
|
</TreeDataTemplate>
|
||||||
|
</u:TreeComboBox.ItemTemplate>
|
||||||
|
</u:TreeComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
||||||
13
demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml.cs
Normal file
13
demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace Ursa.Demo.Pages;
|
||||||
|
|
||||||
|
public partial class TreeComboBoxDemo : UserControl
|
||||||
|
{
|
||||||
|
public TreeComboBoxDemo()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,6 +56,7 @@ public class MainViewViewModel : ViewModelBase
|
|||||||
MenuKeys.MenuKeySkeleton => new SkeletonDemoViewModel(),
|
MenuKeys.MenuKeySkeleton => new SkeletonDemoViewModel(),
|
||||||
MenuKeys.MenuKeyTagInput => new TagInputDemoViewModel(),
|
MenuKeys.MenuKeyTagInput => new TagInputDemoViewModel(),
|
||||||
MenuKeys.MenuKeyTimeline => new TimelineDemoViewModel(),
|
MenuKeys.MenuKeyTimeline => new TimelineDemoViewModel(),
|
||||||
|
MenuKeys.MenuKeyTreeComboBox => new TreeComboBoxDemoViewModel(),
|
||||||
MenuKeys.MenuKeyTwoTonePathIcon => new TwoTonePathIconDemoViewModel(),
|
MenuKeys.MenuKeyTwoTonePathIcon => new TwoTonePathIconDemoViewModel(),
|
||||||
MenuKeys.MenuKeyThemeToggler => new ThemeTogglerDemoViewModel(),
|
MenuKeys.MenuKeyThemeToggler => new ThemeTogglerDemoViewModel(),
|
||||||
MenuKeys.MenuKeyToolBar => new ToolBarDemoViewModel(),
|
MenuKeys.MenuKeyToolBar => new ToolBarDemoViewModel(),
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ public class MenuViewModel: ViewModelBase
|
|||||||
new() { MenuHeader = "TagInput", Key = MenuKeys.MenuKeyTagInput },
|
new() { MenuHeader = "TagInput", Key = MenuKeys.MenuKeyTagInput },
|
||||||
new() { MenuHeader = "Theme Toggler", Key = MenuKeys.MenuKeyThemeToggler },
|
new() { MenuHeader = "Theme Toggler", Key = MenuKeys.MenuKeyThemeToggler },
|
||||||
new() { MenuHeader = "Timeline", Key = MenuKeys.MenuKeyTimeline },
|
new() { MenuHeader = "Timeline", Key = MenuKeys.MenuKeyTimeline },
|
||||||
|
new() { MenuHeader = "TreeComboBox", Key = MenuKeys.MenuKeyTreeComboBox },
|
||||||
new() { MenuHeader = "TwoTonePathIcon", Key = MenuKeys.MenuKeyTwoTonePathIcon},
|
new() { MenuHeader = "TwoTonePathIcon", Key = MenuKeys.MenuKeyTwoTonePathIcon},
|
||||||
new() { MenuHeader = "ToolBar", Key = MenuKeys.MenuKeyToolBar },
|
new() { MenuHeader = "ToolBar", Key = MenuKeys.MenuKeyToolBar },
|
||||||
new() { MenuHeader = "Time Box", Key = MenuKeys.MenuKeyTimeBox, Status = "New" },
|
new() { MenuHeader = "Time Box", Key = MenuKeys.MenuKeyTimeBox, Status = "New" },
|
||||||
@@ -88,6 +89,7 @@ public static class MenuKeys
|
|||||||
public const string MenuKeyTimeline = "Timeline";
|
public const string MenuKeyTimeline = "Timeline";
|
||||||
public const string MenuKeyTwoTonePathIcon = "TwoTonePathIcon";
|
public const string MenuKeyTwoTonePathIcon = "TwoTonePathIcon";
|
||||||
public const string MenuKeyThemeToggler = "ThemeToggler";
|
public const string MenuKeyThemeToggler = "ThemeToggler";
|
||||||
|
public const string MenuKeyTreeComboBox = "TreeComboBox";
|
||||||
public const string MenuKeyToolBar = "ToolBar";
|
public const string MenuKeyToolBar = "ToolBar";
|
||||||
public const string MenuKeyVerificationCode = "VerificationCode";
|
public const string MenuKeyVerificationCode = "VerificationCode";
|
||||||
public const string MenuKeyTimeBox = "TimeBox";
|
public const string MenuKeyTimeBox = "TimeBox";
|
||||||
|
|||||||
68
demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs
Normal file
68
demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace Ursa.Demo.ViewModels;
|
||||||
|
|
||||||
|
public partial class TreeComboBoxDemoViewModel: ObservableObject
|
||||||
|
{
|
||||||
|
[ObservableProperty] private TreeComboBoxItemViewModel? _selectedItem;
|
||||||
|
public List<TreeComboBoxItemViewModel> Items { get; set; }
|
||||||
|
|
||||||
|
public TreeComboBoxDemoViewModel()
|
||||||
|
{
|
||||||
|
Items = new List<TreeComboBoxItemViewModel>()
|
||||||
|
{
|
||||||
|
new TreeComboBoxItemViewModel()
|
||||||
|
{
|
||||||
|
ItemName = "Item 1",
|
||||||
|
Children = new List<TreeComboBoxItemViewModel>()
|
||||||
|
{
|
||||||
|
new TreeComboBoxItemViewModel()
|
||||||
|
{
|
||||||
|
ItemName = "Item 1-1",
|
||||||
|
Children = new List<TreeComboBoxItemViewModel>()
|
||||||
|
{
|
||||||
|
new TreeComboBoxItemViewModel()
|
||||||
|
{
|
||||||
|
ItemName = "Item 1-1-1"
|
||||||
|
},
|
||||||
|
new TreeComboBoxItemViewModel()
|
||||||
|
{
|
||||||
|
ItemName = "Item 1-1-2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new TreeComboBoxItemViewModel()
|
||||||
|
{
|
||||||
|
ItemName = "Item 1-2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new TreeComboBoxItemViewModel()
|
||||||
|
{
|
||||||
|
ItemName = "Item 2",
|
||||||
|
Children = new List<TreeComboBoxItemViewModel>()
|
||||||
|
{
|
||||||
|
new TreeComboBoxItemViewModel()
|
||||||
|
{
|
||||||
|
ItemName = "Item 2-1"
|
||||||
|
},
|
||||||
|
new TreeComboBoxItemViewModel()
|
||||||
|
{
|
||||||
|
ItemName = "Item 2-2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new TreeComboBoxItemViewModel()
|
||||||
|
{
|
||||||
|
ItemName = "Item 3"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class TreeComboBoxItemViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
[ObservableProperty] private string? _itemName;
|
||||||
|
public List<TreeComboBoxItemViewModel> Children { get; set; } = new ();
|
||||||
|
}
|
||||||
298
src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml
Normal file
298
src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:converters="clr-namespace:Avalonia.Controls.Converters;assembly=Avalonia.Controls"
|
||||||
|
xmlns:converters1="clr-namespace:Ursa.Converters;assembly=Ursa"
|
||||||
|
xmlns:iri="https://irihi.tech/shared"
|
||||||
|
xmlns:u="https://irihi.tech/ursa">
|
||||||
|
<!-- Add Resources Here -->
|
||||||
|
|
||||||
|
<converters:MarginMultiplierConverter
|
||||||
|
x:Key="LeftMarginConverter"
|
||||||
|
Indent="20"
|
||||||
|
Left="True" />
|
||||||
|
|
||||||
|
<ControlTheme x:Key="{x:Type u:TreeComboBox}" TargetType="u:TreeComboBox">
|
||||||
|
<Setter Property="Padding" Value="{DynamicResource ComboBoxSelectorDefaultPadding}" />
|
||||||
|
<Setter Property="FocusAdorner" Value="{x:Null}" />
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ComboBoxSelectorBackground}" />
|
||||||
|
<Setter Property="CornerRadius" Value="{DynamicResource ComboBoxSelectorCornerRadius}" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="MinHeight" Value="{DynamicResource ComboBoxDefaultHeight}" />
|
||||||
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<ControlTemplate TargetType="u:TreeComboBox">
|
||||||
|
<Grid MinWidth="{TemplateBinding MinHeight}" ColumnDefinitions="Auto, *, Auto, Auto, Auto">
|
||||||
|
<Border
|
||||||
|
Name="Background"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="5"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
CornerRadius="{TemplateBinding CornerRadius}" />
|
||||||
|
<ContentPresenter
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Content="{TemplateBinding InnerLeftContent}"
|
||||||
|
Foreground="{DynamicResource TextBoxInnerForeground}"
|
||||||
|
IsVisible="{TemplateBinding InnerLeftContent,
|
||||||
|
Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||||||
|
<TextBlock
|
||||||
|
Name="PlaceholderTextBlock"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="{TemplateBinding Padding}"
|
||||||
|
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||||
|
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||||
|
Foreground="{TemplateBinding Foreground}"
|
||||||
|
IsVisible="{TemplateBinding SelectionBoxItem,
|
||||||
|
Converter={x:Static ObjectConverters.IsNull}}"
|
||||||
|
Opacity="0.3"
|
||||||
|
Text="{TemplateBinding Watermark}"
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
<ContentPresenter
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="{TemplateBinding Padding}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||||
|
Content="{TemplateBinding SelectionBoxItem}">
|
||||||
|
<ContentPresenter.ContentTemplate>
|
||||||
|
<MultiBinding Converter="{x:Static converters1:SelectionBoxTemplateConverter.Instance}">
|
||||||
|
<TemplateBinding Property="SelectedItemTemplate" />
|
||||||
|
<TemplateBinding Property="ItemTemplate" />
|
||||||
|
</MultiBinding>
|
||||||
|
</ContentPresenter.ContentTemplate>
|
||||||
|
</ContentPresenter>
|
||||||
|
<Button
|
||||||
|
Name="PART_ClearButton"
|
||||||
|
Grid.Column="2"
|
||||||
|
Command="{Binding $parent[iri:IClearControl].Clear}"
|
||||||
|
IsVisible="False"
|
||||||
|
Theme="{DynamicResource InnerIconButton}"
|
||||||
|
Content="{DynamicResource IconButtonClearData}" />
|
||||||
|
<ContentPresenter
|
||||||
|
Grid.Column="3"
|
||||||
|
Margin="0 0 8 0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Content="{TemplateBinding InnerRightContent}"
|
||||||
|
Foreground="{DynamicResource TextBoxInnerForeground}"
|
||||||
|
IsVisible="{TemplateBinding InnerRightContent,
|
||||||
|
Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||||||
|
<Panel
|
||||||
|
Grid.Column="4"
|
||||||
|
Width="32"
|
||||||
|
Background="Transparent"
|
||||||
|
IsHitTestVisible="True">
|
||||||
|
<PathIcon
|
||||||
|
x:Name="DropDownGlyph"
|
||||||
|
Width="12"
|
||||||
|
Height="12"
|
||||||
|
Margin="0,0,10,0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Data="{DynamicResource ComboBoxIcon}"
|
||||||
|
Foreground="{DynamicResource ComboBoxIconDefaultForeground}"
|
||||||
|
UseLayoutRounding="False" />
|
||||||
|
</Panel>
|
||||||
|
<Popup
|
||||||
|
Name="{x:Static iri:PartNames.PART_Popup}"
|
||||||
|
Grid.Column="0"
|
||||||
|
MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
|
||||||
|
MaxHeight="{TemplateBinding MaxDropDownHeight}"
|
||||||
|
ClipToBounds="False"
|
||||||
|
InheritsTransform="True"
|
||||||
|
IsLightDismissEnabled="True"
|
||||||
|
IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||||
|
PlacementTarget="Background"
|
||||||
|
WindowManagerAddShadowHint="False">
|
||||||
|
<Border
|
||||||
|
Name="PopupBorder"
|
||||||
|
Margin="0,4"
|
||||||
|
BorderThickness="{DynamicResource ComboBoxPopupBorderThickness}"
|
||||||
|
Background="{DynamicResource ComboBoxPopupBackground}"
|
||||||
|
BorderBrush="{DynamicResource ComboBoxPopupBorderBrush}"
|
||||||
|
BoxShadow="{DynamicResource ComboBoxPopupBoxShadow}"
|
||||||
|
ClipToBounds="True"
|
||||||
|
CornerRadius="6">
|
||||||
|
<DockPanel LastChildFill="True">
|
||||||
|
<ContentPresenter
|
||||||
|
Name="PART_PopupHeader"
|
||||||
|
Margin="8,8 8 0"
|
||||||
|
IsVisible="{TemplateBinding PopupInnerTopContent,
|
||||||
|
Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||||
|
Content="{TemplateBinding PopupInnerTopContent}"
|
||||||
|
DockPanel.Dock="Top" />
|
||||||
|
<ContentPresenter
|
||||||
|
Name="PART_PopupFooter"
|
||||||
|
Margin="8 0 8 8"
|
||||||
|
IsVisible="{TemplateBinding PopupInnerBottomContent,
|
||||||
|
Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||||
|
Content="{TemplateBinding PopupInnerBottomContent}"
|
||||||
|
DockPanel.Dock="Bottom" />
|
||||||
|
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}">
|
||||||
|
<ItemsPresenter ItemsPanel="{TemplateBinding ItemsPanel}" />
|
||||||
|
</ScrollViewer>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
</Popup>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter>
|
||||||
|
|
||||||
|
<Style Selector="^.clearButton, ^.ClearButton">
|
||||||
|
<Style Selector="^:pointerover /template/ Button#PART_ClearButton">
|
||||||
|
<Setter Property="IsVisible" Value="{Binding $parent[u:TreeComboBox].SelectionBoxItem, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||||||
|
</Style>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="^.Large">
|
||||||
|
<Setter Property="MinHeight" Value="{DynamicResource ComboBoxLargeHeight}"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="^.Small">
|
||||||
|
<Setter Property="MinHeight" Value="{DynamicResource ComboBoxSmallHeight}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Pointerover State -->
|
||||||
|
<Style Selector="^:pointerover">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ComboBoxSelectorPointeroverBackground}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxSelectorPointeroverBorderBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="^:pointerover /template/ PathIcon#DropDownGlyph">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ComboBoxIconPointeroverForeground}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Pressed State -->
|
||||||
|
<Style Selector="^:pressed">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ComboBoxSelectorPressedBackground}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxSelectorPressedBorderBrush}" />
|
||||||
|
<Style Selector="^ /template/ PathIcon#DropDownGlyph">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ComboBoxIconPressedForeground}" />
|
||||||
|
</Style>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="^:dropdownopen">
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxSelectorPressedBorderBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Disabled State -->
|
||||||
|
<Style Selector="^:disabled">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ComboBoxSelectorDisabledBackground}" />
|
||||||
|
<Style Selector="^ /template/ ContentControl#ContentPresenter">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ComboBoxDisabledForeground}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="^ /template/ TextBlock#PlaceholderTextBlock">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ComboBoxDisabledForeground}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="^ /template/ PathIcon#DropDownGlyph">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ComboBoxIconDisabledForeground}" />
|
||||||
|
</Style>
|
||||||
|
</Style>
|
||||||
|
<!-- Error State -->
|
||||||
|
<Style Selector="^:error">
|
||||||
|
<Style Selector="^ /template/ Border#Background">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource DataValidationErrorsBackground}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="^:pointerover /template/ Border#Background">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource DataValidationErrorsPointerOverBackground}" />
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="^:pressed /template/ Border#Background">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource DataValidationErrorsPressedBackground}" />
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="^:focus /template/ Border#Background">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource DataValidationErrorsSelectedBackground}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource DataValidationErrorsSelectedBorderBrush}" />
|
||||||
|
</Style>
|
||||||
|
</Style>
|
||||||
|
</ControlTheme>
|
||||||
|
|
||||||
|
<ControlTheme x:Key="{x:Type u:TreeComboBoxItem}" TargetType="u:TreeComboBoxItem">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource TreeViewItemDefaultBackground}" />
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TreeViewItemDefaultForeground}" />
|
||||||
|
<Setter Property="CornerRadius" Value="3" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<ControlTemplate TargetType="u:TreeComboBoxItem">
|
||||||
|
<StackPanel>
|
||||||
|
<Border
|
||||||
|
Name="PART_LayoutRoot"
|
||||||
|
MinHeight="{TemplateBinding MinHeight}"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
CornerRadius="{TemplateBinding CornerRadius}"
|
||||||
|
TemplatedControl.IsTemplateFocusTarget="True">
|
||||||
|
<Grid
|
||||||
|
Name="{x:Static iri:PartNames.PART_Header}"
|
||||||
|
Margin="{TemplateBinding Level,
|
||||||
|
Mode=OneWay,
|
||||||
|
Converter={StaticResource LeftMarginConverter}}"
|
||||||
|
ColumnDefinitions="Auto, *">
|
||||||
|
<ToggleButton
|
||||||
|
Name="PART_ExpandCollapseChevron"
|
||||||
|
Grid.Column="0"
|
||||||
|
Padding="{DynamicResource TreeViewItemIconMargin}"
|
||||||
|
Focusable="False"
|
||||||
|
IsChecked="{TemplateBinding IsExpanded,
|
||||||
|
Mode=TwoWay}"
|
||||||
|
Theme="{DynamicResource ToggleButtonTreeViewItemIconButton}" />
|
||||||
|
<ContentPresenter
|
||||||
|
Name="{x:Static iri:PartNames.PART_HeaderPresenter}"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="{TemplateBinding Padding}"
|
||||||
|
Padding="{DynamicResource TreeViewItemPadding}"
|
||||||
|
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||||
|
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||||
|
Content="{TemplateBinding Header}"
|
||||||
|
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||||
|
Focusable="False"
|
||||||
|
Foreground="{TemplateBinding Foreground}" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
<ItemsPresenter
|
||||||
|
Name="{x:Static iri:PartNames.PART_ItemsPresenter}"
|
||||||
|
IsVisible="{TemplateBinding IsExpanded}"
|
||||||
|
ItemsPanel="{TemplateBinding ItemsPanel}" />
|
||||||
|
</StackPanel>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter>
|
||||||
|
|
||||||
|
<!-- Pointerover state -->
|
||||||
|
<Style Selector="^ /template/ Border#PART_LayoutRoot:pointerover">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource TreeViewItemPointeroverBackground}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Pressed state -->
|
||||||
|
<Style Selector="^:pressed /template/ Border#PART_LayoutRoot:pointerover">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource TreeViewItemPressedBackground}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Disabled state -->
|
||||||
|
<Style Selector="^:disabled /template/ Border#PART_LayoutRoot">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource TreeViewItemDisabledBackground}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="^:disabled /template/ ContentPresenter#PART_HeaderPresenter">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TreeViewItemDisabledForeground}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Selected state -->
|
||||||
|
<Style Selector="^:selected /template/ Border#PART_LayoutRoot">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource TreeViewItemSelectedBackground}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Disabled Selected state -->
|
||||||
|
<Style Selector="^:disabled:selected /template/ Border#PART_LayoutRoot">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource TreeViewItemSelectedDisabledBackground}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="^:empty /template/ ToggleButton#PART_ExpandCollapseChevron">
|
||||||
|
<Setter Property="Opacity" Value="0" />
|
||||||
|
<Setter Property="IsHitTestVisible" Value="False" />
|
||||||
|
</Style>
|
||||||
|
</ControlTheme>
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
<ResourceInclude Source="TagInput.axaml" />
|
<ResourceInclude Source="TagInput.axaml" />
|
||||||
<ResourceInclude Source="ThemeSelector.axaml" />
|
<ResourceInclude Source="ThemeSelector.axaml" />
|
||||||
<ResourceInclude Source="Timeline.axaml" />
|
<ResourceInclude Source="Timeline.axaml" />
|
||||||
|
<ResourceInclude Source="TreeComboBox.axaml"/>
|
||||||
<ResourceInclude Source="Skeleton.axaml" />
|
<ResourceInclude Source="Skeleton.axaml" />
|
||||||
<ResourceInclude Source="TwoTonePathIcon.axaml" />
|
<ResourceInclude Source="TwoTonePathIcon.axaml" />
|
||||||
<ResourceInclude Source="ToolBar.axaml" />
|
<ResourceInclude Source="ToolBar.axaml" />
|
||||||
|
|||||||
339
src/Ursa/Controls/ComboBox/TreeComboBox.cs
Normal file
339
src/Ursa/Controls/ComboBox/TreeComboBox.cs
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Metadata;
|
||||||
|
using Avalonia.Controls.Mixins;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Controls.Shapes;
|
||||||
|
using Avalonia.Controls.Templates;
|
||||||
|
using Avalonia.Data;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Metadata;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
using Irihi.Avalonia.Shared.Common;
|
||||||
|
using Irihi.Avalonia.Shared.Contracts;
|
||||||
|
using Irihi.Avalonia.Shared.Helpers;
|
||||||
|
using Size = Avalonia.Size;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
|
[TemplatePart(PartNames.PART_Popup, typeof(Popup))]
|
||||||
|
[PseudoClasses(PC_DropdownOpen)]
|
||||||
|
public class TreeComboBox: ItemsControl, IClearControl, IInnerContentControl, IPopupInnerContent
|
||||||
|
{
|
||||||
|
public const string PC_DropdownOpen = ":dropdownopen";
|
||||||
|
|
||||||
|
private Popup? _popup;
|
||||||
|
|
||||||
|
private static readonly FuncTemplate<Panel?> DefaultPanel =
|
||||||
|
new FuncTemplate<Panel?>(() => new VirtualizingStackPanel());
|
||||||
|
|
||||||
|
public static readonly StyledProperty<double> MaxDropDownHeightProperty =
|
||||||
|
ComboBox.MaxDropDownHeightProperty.AddOwner<TreeComboBox>();
|
||||||
|
|
||||||
|
public double MaxDropDownHeight
|
||||||
|
{
|
||||||
|
get => GetValue(MaxDropDownHeightProperty);
|
||||||
|
set => SetValue(MaxDropDownHeightProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<string?> WatermarkProperty =
|
||||||
|
TextBox.WatermarkProperty.AddOwner<TreeComboBox>();
|
||||||
|
|
||||||
|
public string? Watermark
|
||||||
|
{
|
||||||
|
get => GetValue(WatermarkProperty);
|
||||||
|
set => SetValue(WatermarkProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> IsDropDownOpenProperty =
|
||||||
|
ComboBox.IsDropDownOpenProperty.AddOwner<TreeComboBox>();
|
||||||
|
|
||||||
|
public bool IsDropDownOpen
|
||||||
|
{
|
||||||
|
get => GetValue(IsDropDownOpenProperty);
|
||||||
|
set => SetValue(IsDropDownOpenProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
|
||||||
|
ContentControl.HorizontalContentAlignmentProperty.AddOwner<TreeComboBox>();
|
||||||
|
|
||||||
|
public HorizontalAlignment HorizontalContentAlignment
|
||||||
|
{
|
||||||
|
get => GetValue(HorizontalContentAlignmentProperty);
|
||||||
|
set => SetValue(HorizontalContentAlignmentProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
|
||||||
|
ContentControl.VerticalContentAlignmentProperty.AddOwner<TreeComboBox>();
|
||||||
|
|
||||||
|
public VerticalAlignment VerticalContentAlignment
|
||||||
|
{
|
||||||
|
get => GetValue(VerticalContentAlignmentProperty);
|
||||||
|
set => SetValue(VerticalContentAlignmentProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IDataTemplate?> SelectedItemTemplateProperty =
|
||||||
|
AvaloniaProperty.Register<TreeComboBox, IDataTemplate?>(nameof(SelectedItemTemplate));
|
||||||
|
|
||||||
|
[InheritDataTypeFromItems(nameof(ItemsSource))]
|
||||||
|
public IDataTemplate? SelectedItemTemplate
|
||||||
|
{
|
||||||
|
get => GetValue(SelectedItemTemplateProperty);
|
||||||
|
set => SetValue(SelectedItemTemplateProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DirectProperty<TreeComboBox, object?> SelectionBoxItemProperty = AvaloniaProperty.RegisterDirect<TreeComboBox, object?>(
|
||||||
|
nameof(SelectionBoxItem), o => o.SelectionBoxItem);
|
||||||
|
private object? _selectionBoxItem;
|
||||||
|
public object? SelectionBoxItem
|
||||||
|
{
|
||||||
|
get => _selectionBoxItem;
|
||||||
|
protected set => SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private object? _selectedItem;
|
||||||
|
|
||||||
|
public static readonly DirectProperty<TreeComboBox, object?> SelectedItemProperty =
|
||||||
|
AvaloniaProperty.RegisterDirect<TreeComboBox, object?>(
|
||||||
|
nameof(SelectedItem), o => o.SelectedItem, (o, v) => o.SelectedItem = v,
|
||||||
|
defaultBindingMode: BindingMode.TwoWay);
|
||||||
|
|
||||||
|
public object? SelectedItem
|
||||||
|
{
|
||||||
|
get => _selectedItem;
|
||||||
|
set => SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<object?> InnerLeftContentProperty = AvaloniaProperty.Register<TreeComboBox, object?>(
|
||||||
|
nameof(InnerLeftContent));
|
||||||
|
|
||||||
|
public object? InnerLeftContent
|
||||||
|
{
|
||||||
|
get => GetValue(InnerLeftContentProperty);
|
||||||
|
set => SetValue(InnerLeftContentProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<object?> InnerRightContentProperty = AvaloniaProperty.Register<TreeComboBox, object?>(
|
||||||
|
nameof(InnerRightContent));
|
||||||
|
|
||||||
|
public object? InnerRightContent
|
||||||
|
{
|
||||||
|
get => GetValue(InnerRightContentProperty);
|
||||||
|
set => SetValue(InnerRightContentProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<object?> PopupInnerTopContentProperty = AvaloniaProperty.Register<TreeComboBox, object?>(
|
||||||
|
nameof(PopupInnerTopContent));
|
||||||
|
|
||||||
|
public object? PopupInnerTopContent
|
||||||
|
{
|
||||||
|
get => GetValue(PopupInnerTopContentProperty);
|
||||||
|
set => SetValue(PopupInnerTopContentProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<object?> PopupInnerBottomContentProperty = AvaloniaProperty.Register<TreeComboBox, object?>(
|
||||||
|
nameof(PopupInnerBottomContent));
|
||||||
|
|
||||||
|
public object? PopupInnerBottomContent
|
||||||
|
{
|
||||||
|
get => GetValue(PopupInnerBottomContentProperty);
|
||||||
|
set => SetValue(PopupInnerBottomContentProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TreeComboBox()
|
||||||
|
{
|
||||||
|
ItemsPanelProperty.OverrideDefaultValue<TreeComboBox>(DefaultPanel);
|
||||||
|
FocusableProperty.OverrideDefaultValue<TreeComboBox>(true);
|
||||||
|
SelectedItemProperty.Changed.AddClassHandler<TreeComboBox, object?>((box, args) => box.OnSelectedItemChanged(args));
|
||||||
|
IsDropDownOpenProperty.AffectsPseudoClass<TreeComboBox>(PC_DropdownOpen);
|
||||||
|
PressedMixin.Attach<TreeComboBox>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSelectedItemChanged(AvaloniaPropertyChangedEventArgs<object?> args)
|
||||||
|
{
|
||||||
|
MarkContainerSelection(args.OldValue.Value, false);
|
||||||
|
MarkContainerSelection(args.NewValue.Value, true);
|
||||||
|
UpdateSelectionBoxItem(args.NewValue.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate(e);
|
||||||
|
_popup = e.NameScope.Find<Popup>(PartNames.PART_Popup);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
|
||||||
|
{
|
||||||
|
return NeedsContainer<TreeComboBoxItem>(item, out recycleKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool NeedsContainerInternal(object? item, int index, out object? recycleKey)
|
||||||
|
{
|
||||||
|
return NeedsContainerOverride(item, index, out recycleKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
|
||||||
|
{
|
||||||
|
return new TreeComboBoxItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Control CreateContainerForItemInternal(object? item, int index, object? recycleKey)
|
||||||
|
{
|
||||||
|
return CreateContainerForItemOverride(item, index, recycleKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ContainerForItemPreparedInternal(Control container, object? item, int index)
|
||||||
|
{
|
||||||
|
ContainerForItemPreparedOverride(container, item, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPointerReleased(PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnPointerReleased(e);
|
||||||
|
if (e is { InitialPressMouseButton: MouseButton.Left, Source: Visual source })
|
||||||
|
{
|
||||||
|
if (_popup is not null && _popup.IsOpen && _popup.IsInsidePopup(source))
|
||||||
|
{
|
||||||
|
var container = GetContainerFromEventSource(source);
|
||||||
|
if (container is null) return;
|
||||||
|
var item = TreeItemFromContainer(container);
|
||||||
|
if (item is null) return;
|
||||||
|
if (SelectedItem is not null)
|
||||||
|
{
|
||||||
|
var selectedContainer = TreeContainerFromItem(SelectedItem);
|
||||||
|
if(selectedContainer is TreeComboBoxItem selectedTreeComboBoxItem)
|
||||||
|
{
|
||||||
|
selectedTreeComboBoxItem.IsSelected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.SelectedItem = item;
|
||||||
|
container.IsSelected = true;
|
||||||
|
IsDropDownOpen = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IsDropDownOpen = !IsDropDownOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSelectionBoxItem(object? item)
|
||||||
|
{
|
||||||
|
if(item is null) SelectionBoxItem = null;
|
||||||
|
if (item is ContentControl contentControl)
|
||||||
|
{
|
||||||
|
item = contentControl.Content;
|
||||||
|
}
|
||||||
|
else if(item is HeaderedItemsControl headeredItemsControl)
|
||||||
|
{
|
||||||
|
item = headeredItemsControl.Header;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item is Control control)
|
||||||
|
{
|
||||||
|
if (VisualRoot == null) return;
|
||||||
|
control.Measure(Size.Infinity);
|
||||||
|
SelectionBoxItem = new Rectangle
|
||||||
|
{
|
||||||
|
Width = control.DesiredSize.Width,
|
||||||
|
Height = control.DesiredSize.Height,
|
||||||
|
Fill = new VisualBrush
|
||||||
|
{
|
||||||
|
Visual = control,
|
||||||
|
Stretch = Stretch.None,
|
||||||
|
AlignmentX = AlignmentX.Left,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// TODO: Implement flow direction udpate
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ItemTemplate is null && DisplayMemberBinding is { } binding)
|
||||||
|
{
|
||||||
|
var template = new FuncDataTemplate<object?>((a,_) => new TextBlock
|
||||||
|
{
|
||||||
|
[DataContextProperty] = a,
|
||||||
|
[!TextBlock.TextProperty] = binding,
|
||||||
|
});
|
||||||
|
var textBlock = template.Build(item);
|
||||||
|
SelectionBoxItem = textBlock;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectionBoxItem = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TreeComboBoxItem? GetContainerFromEventSource(object eventSource)
|
||||||
|
{
|
||||||
|
if (eventSource is Visual visual)
|
||||||
|
{
|
||||||
|
var item = visual.GetSelfAndVisualAncestors().OfType<TreeComboBoxItem>().FirstOrDefault();
|
||||||
|
return item?.Owner == this ? item : null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private object? TreeItemFromContainer(Control container)
|
||||||
|
{
|
||||||
|
return TreeItemFromContainer(this, container);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control? TreeContainerFromItem(object item)
|
||||||
|
{
|
||||||
|
return TreeContainerFromItem(this, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Control? TreeContainerFromItem(ItemsControl itemsControl, object item)
|
||||||
|
{
|
||||||
|
if (itemsControl.ContainerFromItem(item) is { } container)
|
||||||
|
{
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
foreach (var child in itemsControl.GetRealizedContainers())
|
||||||
|
{
|
||||||
|
if(child is ItemsControl childItemsControl && TreeContainerFromItem(childItemsControl, item) is { } childContainer)
|
||||||
|
{
|
||||||
|
return childContainer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object? TreeItemFromContainer(ItemsControl itemsControl, Control container)
|
||||||
|
{
|
||||||
|
if (itemsControl.ItemFromContainer(container) is { } item)
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
foreach (var child in itemsControl.GetRealizedContainers())
|
||||||
|
{
|
||||||
|
if(child is ItemsControl childItemsControl && TreeItemFromContainer(childItemsControl, container) is { } childItem)
|
||||||
|
{
|
||||||
|
return childItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MarkContainerSelection(object? item, bool selected)
|
||||||
|
{
|
||||||
|
if (item is null) return;
|
||||||
|
var container = TreeContainerFromItem(item);
|
||||||
|
if (container is TreeComboBoxItem treeComboBoxItem)
|
||||||
|
{
|
||||||
|
treeComboBoxItem.IsSelected = selected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
SelectedItem = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
123
src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs
Normal file
123
src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Metadata;
|
||||||
|
using Avalonia.Controls.Mixins;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.LogicalTree;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Media.TextFormatting;
|
||||||
|
using Irihi.Avalonia.Shared.Common;
|
||||||
|
using Irihi.Avalonia.Shared.Helpers;
|
||||||
|
|
||||||
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
|
[TemplatePart(PartNames.PART_Header, typeof(Control))]
|
||||||
|
public class TreeComboBoxItem: HeaderedItemsControl, ISelectable
|
||||||
|
{
|
||||||
|
private Control? _header;
|
||||||
|
private TreeComboBox? _treeComboBox;
|
||||||
|
public TreeComboBox? Owner => _treeComboBox;
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> IsSelectedProperty = TreeViewItem.IsSelectedProperty.AddOwner<TreeComboBoxItem>();
|
||||||
|
|
||||||
|
public bool IsSelected
|
||||||
|
{
|
||||||
|
get => GetValue(IsSelectedProperty);
|
||||||
|
set => SetValue(IsSelectedProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> IsExpandedProperty = TreeViewItem.IsExpandedProperty.AddOwner<TreeComboBoxItem>();
|
||||||
|
|
||||||
|
public bool IsExpanded
|
||||||
|
{
|
||||||
|
get => GetValue(IsExpandedProperty);
|
||||||
|
set => SetValue(IsExpandedProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static readonly DirectProperty<TreeComboBoxItem, int> LevelProperty = AvaloniaProperty.RegisterDirect<TreeComboBoxItem, int>(
|
||||||
|
nameof(Level), o => o.Level, (o, v) => o.Level = v);
|
||||||
|
private int _level;
|
||||||
|
public int Level
|
||||||
|
{
|
||||||
|
get => _level;
|
||||||
|
protected set => SetAndRaise(LevelProperty, ref _level, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TreeComboBoxItem()
|
||||||
|
{
|
||||||
|
IsSelectedProperty.AffectsPseudoClass<TreeComboBoxItem>(PseudoClassName.PC_Selected,
|
||||||
|
SelectingItemsControl.IsSelectedChangedEvent);
|
||||||
|
PressedMixin.Attach<TreeComboBoxItem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate(e);
|
||||||
|
DoubleTappedEvent.RemoveHandler(OnDoubleTapped, this);
|
||||||
|
_header = e.NameScope.Find<Control>(PartNames.PART_Header);
|
||||||
|
DoubleTappedEvent.AddHandler(OnDoubleTapped, RoutingStrategies.Tunnel, true, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnAttachedToLogicalTree(e);
|
||||||
|
_treeComboBox = this.FindLogicalAncestorOfType<TreeComboBox>();
|
||||||
|
Level = CalculateDistanceFromLogicalParent<TreeComboBox>(this) - 1;
|
||||||
|
if (this.ItemTemplate is null && this._treeComboBox?.ItemTemplate is not null)
|
||||||
|
{
|
||||||
|
SetCurrentValue(ItemTemplateProperty, this._treeComboBox.ItemTemplate);
|
||||||
|
}
|
||||||
|
if(this.ItemContainerTheme is null && this._treeComboBox?.ItemContainerTheme is not null)
|
||||||
|
{
|
||||||
|
SetCurrentValue(ItemContainerThemeProperty, this._treeComboBox.ItemContainerTheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDoubleTapped(object sender, TappedEventArgs e)
|
||||||
|
{
|
||||||
|
if (this.ItemCount <= 0) return;
|
||||||
|
this.SetCurrentValue(IsExpandedProperty, !IsExpanded);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
|
||||||
|
{
|
||||||
|
TreeViewItem t = new TreeViewItem();
|
||||||
|
ComboBox c = new ComboBox();
|
||||||
|
return EnsureParent().NeedsContainerInternal(item, index, out recycleKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
|
||||||
|
{
|
||||||
|
return EnsureParent().CreateContainerForItemInternal(item, index, recycleKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ContainerForItemPreparedOverride(Control container, object? item, int index)
|
||||||
|
{
|
||||||
|
EnsureParent().ContainerForItemPreparedInternal(container, item, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO replace with helper method from shared library.
|
||||||
|
private static int CalculateDistanceFromLogicalParent<T>(ILogical? logical, int @default = -1) where T: ILogical
|
||||||
|
{
|
||||||
|
int distance = 0;
|
||||||
|
ILogical? parent = logical;
|
||||||
|
while (parent is not null)
|
||||||
|
{
|
||||||
|
if (parent is T) return distance;
|
||||||
|
parent = parent.LogicalParent;
|
||||||
|
distance++;
|
||||||
|
}
|
||||||
|
return @default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TreeComboBox EnsureParent()
|
||||||
|
{
|
||||||
|
return this._treeComboBox ??
|
||||||
|
throw new InvalidOperationException("TreeComboBoxItem must be a part of TreeComboBox");
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/Ursa/Converters/SelectionBoxTemplateConverter.cs
Normal file
19
src/Ursa/Converters/SelectionBoxTemplateConverter.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using Avalonia.Controls.Templates;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
|
|
||||||
|
namespace Ursa.Converters;
|
||||||
|
|
||||||
|
public class SelectionBoxTemplateConverter: IMultiValueConverter
|
||||||
|
{
|
||||||
|
public static SelectionBoxTemplateConverter Instance { get; } = new();
|
||||||
|
|
||||||
|
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < values.Count; i++)
|
||||||
|
{
|
||||||
|
if (values[i] is IDataTemplate template) return template;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)"/>
|
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)"/>
|
||||||
<PackageReference Include="Irihi.Avalonia.Shared" Version="0.1.5"/>
|
<PackageReference Include="Irihi.Avalonia.Shared" Version="0.1.7" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user