■ ListView 엘리먼트의 GroupStyle 속성을 사용해 그룹 헤더를 표시하는 방법을 보여준다.
▶ NativeHelper.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 |
using System.Runtime.InteropServices; namespace TestProject { /// <summary> /// 네이티브 헬퍼 /// </summary> public class NativeHelper { //////////////////////////////////////////////////////////////////////////////////////////////////// Import ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 현재 패키지 ID 구하기 - GetCurrentPackageId(bufferLength, buffer) /// <summary> /// 현재 패키지 ID 구하기 /// </summary> /// <param name="bufferLength">버퍼 길이</param> /// <param name="buffer">버퍼</param> /// <returns>처리 결과</returns> [DllImport("api-ms-win-appmodel-runtime-l1-1-1", SetLastError = true)] [return : MarshalAs(UnmanagedType.U4)] private static extern uint GetCurrentPackageId(ref int bufferLength, out byte buffer); #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Public #region Field /// <summary> /// ERROR_SUCCESS /// </summary> public const int ERROR_SUCCESS = 0; /// <summary> /// ERROR_INSUFFICIENT_BUFFER /// </summary> public const int ERROR_INSUFFICIENT_BUFFER = 122; /// <summary> /// APPMODEL_ERROR_NO_PACKAGE /// </summary> public const int APPMODEL_ERROR_NO_PACKAGE = 15700; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 애플리케이션 패키지 여부 - IsAppPackaged /// <summary> /// 애플리케이션 패키지 여부 /// </summary> public static bool IsAppPackaged { get { int bufferSize = 0; uint lastError = GetCurrentPackageId(ref bufferSize, out _); bool isPackaged = true; if(lastError == APPMODEL_ERROR_NO_PACKAGE) { isPackaged = false; } return isPackaged; } } #endregion } } |
▶ FileLoader.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 |
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Windows.Storage; namespace TestProject { /// <summary> /// 파일 로더 /// </summary> public class FileLoader { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 텍스트 로드하기 - LoadText(relativeFilePath) /// <summary> /// 텍스트 로드하기 /// </summary> /// <param name="relativeFilePath">상대적인 파일 경로</param> /// <returns>텍스트</returns> public static async Task<string> LoadText(string relativeFilePath) { StorageFile storageFile = null; if(!NativeHelper.IsAppPackaged) { string entryAssemblyFilePath = Assembly.GetEntryAssembly().Location; string entryAssemblyDirectorPath = Path.GetDirectoryName(entryAssemblyFilePath); string filePath = Path.Combine(entryAssemblyDirectorPath, relativeFilePath); string finalFilePath = Path.GetFullPath(filePath); storageFile = await StorageFile.GetFileFromPathAsync(finalFilePath); } else { Uri sourceURI = new Uri($"ms-appx:///{relativeFilePath}"); storageFile = await StorageFile.GetFileFromApplicationUriAsync(sourceURI); } return await FileIO.ReadTextAsync(storageFile); } #endregion #region 라인 리스트 로드하기 - LoadLineList(relativeFilePath) /// <summary> /// 라인 리스트 로드하기 /// </summary> /// <param name="relativeFilePath">상대적인 파일 경로</param> /// <returns>라인 리스트</returns> public static async Task<IList<string>> LoadLineList(string relativeFilePath) { string text = await LoadText(relativeFilePath); return text.Split(Environment.NewLine).ToList(); } #endregion } } |
▶ GroupInfoList.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 |
using System.Collections.Generic; namespace TestProject { /// <summary> /// 그룹 정보 리스트 /// </summary> public class GroupInfoList : List<object> { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 키 - Key /// <summary> /// 키 /// </summary> public object Key { get; set; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - GroupInfoList(itemEnumerable) /// <summary> /// 생성자 /// </summary> /// <param name="itemEnumerable">항목 열거 가능형</param> public GroupInfoList(IEnumerable<object> itemEnumerable) : base(itemEnumerable) { } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 문자열 구하기 - ToString() /// <summary> /// 문자열 구하기 /// </summary> /// <returns></returns> public override string ToString() { return $"Group {Key}"; } #endregion } } |
▶ Contact.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 126 |
using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; namespace TestProject { /// <summary> /// 연락처 /// </summary> public class Contact { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 이름 - FirstName /// <summary> /// 이름 /// </summary> public string FirstName { get; private set; } #endregion #region 성 - LastName /// <summary> /// 성 /// </summary> public string LastName { get; private set; } #endregion #region 회사 - Company /// <summary> /// 회사 /// </summary> public string Company { get; private set; } #endregion #region 성명 - Name /// <summary> /// 성명 /// </summary> public string Name => $"{FirstName} {LastName}"; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - Contact(firstName, lastName, company) /// <summary> /// 생성자 /// </summary> /// <param name="firstName">이름</param> /// <param name="lastName">성</param> /// <param name="company">회사</param> public Contact(string firstName, string lastName, string company) { FirstName = firstName; LastName = lastName; Company = company; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 연락처 컬렉션 구하기 (비동기) - GetContactCollectionAsync() /// <summary> /// 연락처 컬렉션 구하기 (비동기) /// </summary> /// <returns>연락처 컬렉션 태스크</returns> public async static Task<ObservableCollection<Contact>> GetContactCollectionAsync() { IList<string> lineList = await FileLoader.LoadLineList("DATA/Contacts.txt"); ObservableCollection<Contact> contactCollection = new ObservableCollection<Contact>(); for(int i = 0; i < lineList.Count - 2; i += 3) { contactCollection.Add(new Contact(lineList[i], lineList[i + 1], lineList[i + 2])); } return contactCollection; } #endregion #region 그룹 연락처 컬렉션 구하기 (비동기) - GetGroupedContactCollectionAsync() /// <summary> /// 그룹 연락처 컬렉션 구하기 (비동기) /// </summary> /// <returns>그룹 연락처 컬렉션 태스크</returns> public static async Task<ObservableCollection<GroupInfoList>> GetGroupedContactCollectionAsync() { var query = from item in await GetContactCollectionAsync() group item by item.LastName.Substring(0, 1).ToUpper() into g orderby g.Key select new GroupInfoList(g) { Key = g.Key }; return new ObservableCollection<GroupInfoList>(query); } #endregion #region 문자열 구하기 - ToString() /// <summary> /// 문자열 구하기 /// </summary> /// <returns>문자열</returns> public override string ToString() { return Name; } #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 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 |
<?xml version="1.0" encoding="utf-8"?> <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="using:TestProject" Title="TestProject"> <Grid Name="grid" Margin="10"> <Grid.Resources> <x:Double x:Key="TitleTextBlockFontSizeKey">28</x:Double> <x:Double x:Key="BodyTextBlockFontSizeKey">14</x:Double> <Style x:Key="BaseTextBlockStyleKey" TargetType="TextBlock"> <Setter Property="LineStackingStrategy" Value="MaxHeight" /> <Setter Property="TextTrimming" Value="CharacterEllipsis" /> <Setter Property="TextWrapping" Value="Wrap" /> <Setter Property="TextLineBounds" Value="Full" /> <Setter Property="FontFamily" Value="XamlAutoFontFamily" /> <Setter Property="FontSize" Value="{StaticResource BodyTextBlockFontSizeKey}" /> <Setter Property="FontWeight" Value="SemiBold" /> </Style> <Style x:Key="TitleTextBlockStyleKey" TargetType="TextBlock" BasedOn="{StaticResource BaseTextBlockStyleKey}"> <Setter Property="OpticalMarginAlignment" Value="TrimSideBearings" /> <Setter Property="FontSize" Value="{StaticResource TitleTextBlockFontSizeKey}" /> </Style> <Style x:Key="TextBlockStyleKey" TargetType="TextBlock"> <Setter Property="LineStackingStrategy" Value="MaxHeight" /> <Setter Property="TextTrimming" Value="CharacterEllipsis" /> <Setter Property="TextWrapping" Value="Wrap" /> <Setter Property="TextLineBounds" Value="Full" /> <Setter Property="FontFamily" Value="XamlAutoFontFamily" /> <Setter Property="FontSize" Value="{StaticResource BodyTextBlockFontSizeKey}" /> <Setter Property="FontWeight" Value="SemiBold" /> </Style> <DataTemplate x:Key="ListViewDataTemplateKey" x:DataType="local:Contact"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Ellipse x:Name="Ellipse" Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5" Width ="32" Height="32" Fill="{ThemeResource SystemControlBackgroundBaseMediumBrush}" /> <TextBlock Grid.Row="0" Grid.Column="1" Style="{ThemeResource TextBlockStyleKey}" Margin="10 5 0 0" x:Phase="1" Text="{x:Bind Name}" /> <TextBlock Grid.Row="1" Grid.Column="1" Style="{ThemeResource BodyTextBlockStyle}" Margin="10 0 0 5" x:Phase="2" Text="{x:Bind Company}" /> </Grid> </DataTemplate> <CollectionViewSource x:Name="collectionViewSource" IsSourceGrouped="True" /> </Grid.Resources> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="10" /> <ColumnDefinition Width="150" /> </Grid.ColumnDefinitions> <ListView Name="listView" Grid.Column="0" ItemTemplate="{StaticResource ListViewDataTemplateKey}" BorderThickness="1" BorderBrush="{ThemeResource SystemControlForegroundBaseMediumLowBrush}" SelectionMode="Single" ShowsScrollingPlaceholders="True" ItemsSource="{x:Bind collectionViewSource.View, Mode=OneWay}"> <ListView.ItemsPanel> <ItemsPanelTemplate> <ItemsStackPanel Name="itemsStackPanel" AreStickyGroupHeadersEnabled="False" Loaded="itemsStackPanel_loaded"/> </ItemsPanelTemplate> </ListView.ItemsPanel> <ListView.GroupStyle> <GroupStyle > <GroupStyle.HeaderTemplate> <DataTemplate x:DataType="local:GroupInfoList"> <Border> <TextBlock Style="{ThemeResource TitleTextBlockStyleKey}" Text="{x:Bind Key}" /> </Border> </DataTemplate> </GroupStyle.HeaderTemplate> </GroupStyle> </ListView.GroupStyle> </ListView> <StackPanel Grid.Column="2"> <ToggleSwitch Name="toggleSwitch" Header="Sticky Headers" IsOn="False" /> </StackPanel> </Grid> </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 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 |
using System.Collections.ObjectModel; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; namespace TestProject { /// <summary> /// 메인 윈도우 /// </summary> public sealed partial class MainWindow : Window { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 아이템 스택 패널 /// </summary> private ItemsStackPanel itemsStackPanel; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - MainWindow() /// <summary> /// 생성자 /// </summary> public MainWindow() { InitializeComponent(); this.grid.Loaded += grid_Loaded; this.toggleSwitch.Toggled += toggleSwitch_Toggled; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Private #region 그리드 로드시 처리하기 - grid_Loaded(sender, e) /// <summary> /// 그리드 로드시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private async void grid_Loaded(object sender, RoutedEventArgs e) { ObservableCollection<GroupInfoList> sourceCollection = await Contact.GetGroupedContactCollectionAsync(); this.collectionViewSource.Source = sourceCollection; } #endregion #region 토글 스위치 토글시 처리하기 - toggleSwitch_Toggled(sender, e) /// <summary> /// 토글 스위치 토글시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void toggleSwitch_Toggled(object sender, RoutedEventArgs e) { if(this.toggleSwitch != null) { if(this.toggleSwitch.IsOn == true) { this.itemsStackPanel.AreStickyGroupHeadersEnabled = true; } else { this.itemsStackPanel.AreStickyGroupHeadersEnabled = false; } } } #endregion #region 아이템 스택 패널 로드시 처리하기 - itemsStackPanel_loaded(sender, e) /// <summary> /// 아이템 스택 패널 로드시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void itemsStackPanel_loaded(object sender, RoutedEventArgs e) { this.itemsStackPanel = sender as ItemsStackPanel; } #endregion } } |