■ ListView 클래스의 DragItemsStarting/DragEnter/DragOver/Drop 이벤트를 사용해 드래그 드롭을 처리하는 방법을 보여준다.
▶ 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 |
<?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> <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 BodyTextBlockFontSize}" /> <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> </Grid.Resources> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="10" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <ListView Name="listView1" Grid.Column="0" ItemTemplate="{StaticResource ListViewDataTemplateKey}" BorderThickness="1" BorderBrush="{ThemeResource SystemControlForegroundBaseMediumLowBrush}" SelectionMode="Single" CanDragItems="True" CanReorderItems="True" AllowDrop="True" /> <ListView Name="listView2" Grid.Column="2" ItemTemplate="{StaticResource ListViewDataTemplateKey}" BorderThickness="1" BorderBrush="{ThemeResource SystemControlForegroundBaseMediumLowBrush}" SelectionMode="Single" CanDragItems="True" CanReorderItems="True" AllowDrop="True" /> </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 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 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
using System; using System.Collections.ObjectModel; using System.Text; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Windows.ApplicationModel.DataTransfer; using Windows.Foundation; namespace TestProject { /// <summary> /// 메인 윈도우 /// </summary> public sealed partial class MainWindow : Window { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 연락처 컬렉션 1 /// </summary> private ObservableCollection<Contact> contactCollection1 = new ObservableCollection<Contact>(); /// <summary> /// 연락처 컬렉션 2 /// </summary> private ObservableCollection<Contact> contactCollection2 = new ObservableCollection<Contact>(); #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - MainWindow() /// <summary> /// 생성자 /// </summary> public MainWindow() { InitializeComponent(); this.grid.Loaded += grid_Loaded; this.listView1.DragItemsStarting += listView1_DragItemsStarting; this.listView1.DragOver += listView1_DragOver; this.listView1.Drop += listView_Drop; this.listView2.DragItemsStarting += listView2_DragItemsStarting; this.listView2.DragEnter += listView2_DragEnter; this.listView2.DragOver += listView2_DragOver; this.listView2.Drop += listView_Drop; } #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) { this.contactCollection1 = await Contact.GetContactCollectionAsync(); this.listView1.ItemsSource = this.contactCollection1; this.contactCollection2.Add(new Contact("John" , "Doe" , "ABC Printers" )); this.contactCollection2.Add(new Contact("Jane" , "Doe" , "XYZ Refrigerators" )); this.contactCollection2.Add(new Contact("Santa", "Claus", "North Pole Toy Factory Inc.")); this.listView2.ItemsSource = this.contactCollection2; } #endregion #region 리스트 뷰 1 아이템 드래그 시작시 처리하기 - listView1_DragItemsStarting(sender, e) /// <summary> /// 리스트 뷰 1 아이템 드래그 시작시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void listView1_DragItemsStarting(object sender, DragItemsStartingEventArgs e) { StringBuilder stringBuilder = new StringBuilder(); foreach(Contact contact in e.Items) { if(stringBuilder.Length > 0) { stringBuilder.AppendLine(); } if(contact.ToString() != null) { stringBuilder.Append($"{contact.FirstName} {contact.LastName} {contact.Company}"); } } e.Data.SetText(stringBuilder.ToString()); e.Data.RequestedOperation = DataPackageOperation.Move; } #endregion #region 리스트 뷰 1 드래그 오버 처리하기 - listView1_DragOver(sender, e) /// <summary> /// 리스트 뷰 1 드래그 오버 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void listView1_DragOver(object sender, DragEventArgs e) { e.AcceptedOperation = DataPackageOperation.Move; } #endregion #region 리스트 뷰 2 아이템 드래그 시작시 처리하기 - listView2_DragItemsStarting(sender, e) /// <summary> /// 리스트 뷰 2 아이템 드래그 시작시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void listView2_DragItemsStarting(object sender, DragItemsStartingEventArgs e) { if(e.Items.Count == 1) { Contact contact = (Contact)e.Items[0]; e.Data.SetText($"{contact.FirstName} {contact.LastName} {contact.Company}"); e.Data.RequestedOperation = DataPackageOperation.Move; } } #endregion #region 리스트 뷰 2 드래그 엔터 처리하기 - listView2_DragEnter(sender, e) /// <summary> /// 리스트 뷰 2 드래그 엔터 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void listView2_DragEnter(object sender, DragEventArgs e) { e.DragUIOverride.IsGlyphVisible = false; } #endregion #region 리스트 뷰 2 드래그 오버 처리하기 - listView2_DragOver(sender, e) /// <summary> /// 리스트 뷰 2 드래그 오버 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void listView2_DragOver(object sender, DragEventArgs e) { e.AcceptedOperation = DataPackageOperation.Move; } #endregion #region 리스트 뷰 드롭 처리하기 - listView_Drop(sender, e) /// <summary> /// 리스트 뷰 드롭 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private async void listView_Drop(object sender, DragEventArgs e) { ListView listView = sender as ListView; if(e.DataView.Contains(StandardDataFormats.Text)) { DragOperationDeferral defferal = e.GetDeferral(); string text = await e.DataView.GetTextAsync(); string[] itemArray = text.Split('\n'); foreach(string item in itemArray) { string[] tokenArray = item.Split(" ", 3); Contact sourceContact = new Contact(tokenArray[0], tokenArray[1], tokenArray[2]); Point point = e.GetPosition(listView.ItemsPanelRoot); int index = 0; if(listView.Items.Count != 0) { ListViewItem sourceItem = (ListViewItem)listView.ContainerFromIndex(0); double itemHeight = sourceItem.ActualHeight + sourceItem.Margin.Top + sourceItem.Margin.Bottom; index = Math.Min(listView.Items.Count - 1, (int)(point.Y / itemHeight)); ListViewItem targetItem = (ListViewItem)listView.ContainerFromIndex(index); Point positionInItem = e.GetPosition(targetItem); if(positionInItem.Y > itemHeight / 2) { index++; } index = Math.Min(listView.Items.Count, index); } if(listView.Name == "listView1") { this.contactCollection1.Insert(index, sourceContact); foreach(Contact contact in this.listView2.Items) { if(contact.FirstName == sourceContact.FirstName && contact.LastName == sourceContact.LastName && contact.Company == sourceContact.Company) { this.contactCollection2.Remove(contact); break; } } } else if(listView.Name == "listView2") { this.contactCollection2.Insert(index, sourceContact); foreach(Contact contact in listView1.Items) { if(contact.FirstName == sourceContact.FirstName && contact.LastName == sourceContact.LastName && contact.Company == sourceContact.Company) { this.contactCollection1.Remove(contact); break; } } } } e.AcceptedOperation = DataPackageOperation.Move; defferal.Complete(); } } #endregion } } |