■ ListBox 클래스에서 확장 가능한 리스트 박스를 사용하는 방법을 보여준다.
[TestLibrary 프로젝트]
▶ ExpandableListBoxStyle.xaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:TestLibrary"> <Style x:Key="BorderStyleKey" TargetType="{x:Type Border}"> <Setter Property="BorderThickness" Value="0 1" /> <Setter Property="BorderBrush" Value="#ff484848" /> <Setter Property="Background" Value="#ff525252" /> </Style> <Style x:Key="ExpanderToggleButtonStyleKey" TargetType="{x:Type ToggleButton}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ToggleButton}"> <Border Name="border" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}"> <TextBlock Name="iconTextBlock" HorizontalAlignment="Center" VerticalAlignment="Center" Text="▽" /> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="border" Property="BorderBrush" Value="#ff363636" /> <Setter TargetName="border" Property="Background" Value="#ff424343" /> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter TargetName="border" Property="BorderBrush" Value="#ff2d2d2d" /> <Setter TargetName="border" Property="Background" Value="#FF373838" /> </Trigger> <Trigger Property="IsChecked" Value="True"> <Setter TargetName="iconTextBlock" Property="Text" Value="△" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="ExpanderStyleKey" TargetType="{x:Type Expander}"> <Setter Property="IsExpanded" Value="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Expander}"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid Grid.Row="0" Height="35"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <Border Grid.Column="0" Style="{StaticResource BorderStyleKey}"> <TextBlock VerticalAlignment="Center" Margin="22 0 0 0" Foreground="#FFDDDDDD" Text="{TemplateBinding Header}" /> </Border> <ToggleButton Grid.Column="1" Style="{DynamicResource ExpanderToggleButtonStyleKey}" Width="28" Height="35" BorderThickness="1 1 0 1" BorderBrush="#FF484848" Background="#ff4d4e4e" IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" /> </Grid> <ContentPresenter Name="ExpandSite" Grid.Row="1" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" Focusable="false" Visibility="Collapsed" /> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsExpanded" Value="true"> <Setter TargetName="ExpandSite" Property="Visibility" Value="Visible" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="ListBoxItemStyleKey" TargetType="{x:Type ListBoxItem}"> <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" /> <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" /> <Setter Property="Focusable" Value="False" /> <Setter Property="Background" Value="Transparent" /> <Setter Property="Padding" Value="0" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <Expander Style="{StaticResource ExpanderStyleKey}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Header="{Binding Header}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true"> <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> </Expander> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="{x:Type local:ExpandableListBox}"> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> <Setter Property="VerticalContentAlignment" Value="Stretch" /> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" /> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Disabled" /> <Setter Property="ScrollViewer.CanContentScroll" Value="true" /> <Setter Property="ScrollViewer.PanningMode" Value="Both" /> <Setter Property="Stylus.IsFlicksEnabled" Value="False" /> <Setter Property="ItemContainerStyle" Value="{StaticResource ListBoxItemStyleKey}"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBox}"> <ScrollViewer Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" Focusable="false"> <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> </ScrollViewer> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary> |
▶ ExpandableListBox.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; namespace TestLibrary { /// <summary> /// 확장 가능한 리스트 박스 /// </summary> public class ExpandableListBox : ListBox { //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Static #region 생성자 - ExpandableListBox() /// <summary> /// 생성자 /// </summary> static ExpandableListBox() { DefaultStyleKeyProperty.OverrideMetadata ( typeof(ExpandableListBox), new FrameworkPropertyMetadata(typeof(ExpandableListBox)) ); } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Protected #region 초기화시 처리하기 - OnInitialized(e) /// <summary> /// 초기화시 처리하기 /// </summary> /// <param name="e">이벤트 인자</param> protected override void OnInitialized(EventArgs e) { base.OnInitialized(e); ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private #region 항목 컨테이너 제너레이터 상태 변경시 처리하기 - ItemContainerGenerator_StatusChanged(sender, e) /// <summary> /// 항목 컨테이너 제너레이터 상태 변경시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) { try { double actualHeight = ActualHeight; if(ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { IEnumerable<object> itemEnumerable = Items.OfType<object>(); ListBoxItem firstOfvalidListBoxItems = ( from item in itemEnumerable let listBoxItem = ItemContainerGenerator.ContainerFromItem(item) as ListBoxItem where listBoxItem.IsSelected == false && listBoxItem.ActualHeight != 0 select listBoxItem ).FirstOrDefault(); if(firstOfvalidListBoxItems == null) { return; } double collapsedHeight = firstOfvalidListBoxItems.ActualHeight * Items.Count; foreach(object item in itemEnumerable) { ListBoxItem listBoxItem = ItemContainerGenerator.ContainerFromItem(item) as ListBoxItem; Expander expander = VisualTreeHelper.GetChild(listBoxItem, 0) as Expander; if(expander == null) { break; } FrameworkElement element = expander.Content as FrameworkElement; if(element == null) { break; } double elementHeight = (actualHeight - collapsedHeight); if(elementHeight >= 0) { element.Height = elementHeight; } } } } catch(Exception exception) { Debug.WriteLine(exception); } } #endregion } } |
[TestProject 프로젝트]
▶ MenuItem.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
using System.Windows; namespace TestProject { /// <summary> /// 메뉴 항목 /// </summary> public class MenuItem : DependencyObject { //////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 헤더 속성 - HeaderProperty /// <summary> /// 헤더 속성 /// </summary> public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register ( "Header", typeof(object), typeof(MenuItem), new PropertyMetadata(null) ); #endregion #region 컨텐트 속성 - ContentProperty /// <summary> /// 컨텐트 속성 /// </summary> public static readonly DependencyProperty ContentProperty = DependencyProperty.Register ( "Content", typeof(object), typeof(MenuItem), new PropertyMetadata(null) ); #endregion #region 확장 여부 속성 - IsExpandedProperty /// <summary> /// 확장 여부 속성 /// </summary> public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register ( "IsExpanded", typeof(bool), typeof(MenuItem), new PropertyMetadata(false) ); #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 헤더 - Header /// <summary> /// 헤더 /// </summary> public object Header { get { return (object)GetValue(HeaderProperty); } set { SetValue(HeaderProperty, value); } } #endregion #region 컨텐트 - Content /// <summary> /// 컨텐트 /// </summary> public object Content { get { return (object)GetValue(ContentProperty); } set { SetValue(ContentProperty, value); } } #endregion #region 확장 여부 - IsExpanded /// <summary> /// 확장 여부 /// </summary> public bool IsExpanded { get { return (bool)GetValue(IsExpandedProperty); } set { SetValue(IsExpandedProperty, value); } } #endregion } } |
▶ MainWindow.xaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<Window x:Class="TestProject.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:TestLibrary;assembly=TestLibrary" Width="800" Height="600" Title="ListBox 클래스 : 확장 가능한 리스트 박스 사용하기" FontFamily="나눔고딕코딩" FontSize="16"> <DockPanel LastChildFill="False"> <local:ExpandableListBox x:Name="expandableListBox" DockPanel.Dock="Left" Width="300" Background="Gray"> <local:ExpandableListBox.ItemTemplate> <DataTemplate> <StackPanel Margin="10"> <TextBlock Text="{Binding Content}" /> </StackPanel> </DataTemplate> </local:ExpandableListBox.ItemTemplate> </local:ExpandableListBox> </DockPanel> </Window> |
▶ MainWindow.xaml.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
using System.Collections.ObjectModel; using System.Windows; namespace TestProject { /// <summary> /// 메인 윈도우 /// </summary> public partial class MainWindow : Window { //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - MainWindow() /// <summary> /// 생성자 /// </summary> public MainWindow() { InitializeComponent(); ObservableCollection<MenuItem> collection = new ObservableCollection<MenuItem>(); collection.Add(new MenuItem { Header = "메일" , Content = "메일 컨텐트" }); collection.Add(new MenuItem { Header = "달력" , Content = "달력 컨텐트" }); collection.Add(new MenuItem { Header = "연락처", Content = "연락처 컨텐트" }); collection.Add(new MenuItem { Header = "일정표", Content = "일정표 컨텐트" }); this.expandableListBox.ItemsSource = collection; } #endregion } } |