Windows GUIプログラミング入門24 ドラッグ＆ドロップ

# ■はじめに

今回はエクスプローラーからファイルのドラッグ＆ドロップを処理します。
最初にドロップされたファイルのフルパス一覧を表示するサンプルを作った後、
ドラッグ＆ドロップでテキストファイルの内容を表示するビューアを作成します。
![00_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/8926bda4-fa1f-618f-8c93-701ed90f6692.png)

# ■開発環境

* Windows 10
* Visual Studio Community 2019 （Version 16.4.5）
* .NET Framework 4.5.2 / .NET Core 3.1

# ■ドラッグ＆ドロップのサンプル

## ◇プロジェクト作成

`WPFアプリ(.NET Framework)`または`WPF App(.NET Core)`でプロジェクトを作成します。
ここでは`.NET Framework`で作成していますが、`.NET Core`でも作成方法は同じです。

## ◇画面

`TextBlock`を配置します。
![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/df3940cd-0b4a-ba0d-3d6e-8271890bd092.png)

`TextBlock`の`AllowDrop`プロパティを`True`にし、`PreviewDragOver`イベントと`Drop`イベントを作成します。

```Xml:MainWindow.xaml
<Window x:Class="DragDropSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        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:local="clr-namespace:DragDropSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TextBlock 
            Text="" 
            Margin="5" 
            AllowDrop="True"
            PreviewDragOver="TextBlock_PreviewDragOver" 
            Drop="TextBlock_Drop"/>
    </Grid>
</Window>
```

## ◇ロジック

```CSharp:MainWindow.xaml.cs
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace DragDropSample
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// ドラッグ中オブジェクトがテキストの上に来た時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TextBlock_PreviewDragOver(object sender, DragEventArgs e)
        {
            // マウスカーソルをコピー中に変更
            e.Effects = DragDropEffects.Copy;
            // ドラッグ中のオブジェクトがファイルの場合、受け付ける
            e.Handled = e.Data.GetDataPresent(DataFormats.FileDrop);
        }

        /// <summary>
        /// テキストにドロップ
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TextBlock_Drop(object sender, DragEventArgs e)
        {
            var txt = sender as TextBlock;
            // ドロップしたファイル一覧を取得
            var files = e.Data.GetData(DataFormats.FileDrop) as string[];
            if (files == null)
            {
                txt.Text = "";
                return;
            }

            var sb = new StringBuilder();
            foreach (string file in files)
            {
                sb.Append(file).Append("\r\n");
            }
            // 表示
            txt.Text = sb.ToString();
        }
    }
}
```

## ◇動かしてみる

画面にファイルをドロップします。
![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/342cb80e-f7da-685a-c0f2-93e12e6e9be7.png)

ファイルのフルパスが表示されました。
![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/19df88a8-b6b5-9281-1190-0f17bc5670c4.png)

次はテキストファイルビューアを作ってみます。
ウィンドウにファイルをドラッグ＆ドロップすると内容を表示します。
ドラッグ＆ドロップ部分の実装だけ知りたかった人はここから先は読む必要ありません。

* [テキストビューア .NET Framework版](https://qiita.com/Kosen-amai/items/231969eef137c770cc8c#net-framework%E7%89%88)
* [テキストビューア .NET Core版](https://qiita.com/Kosen-amai/items/231969eef137c770cc8c#net-core%E7%89%88)

# ■テキストビューア

## ◇.NET Framework版

### ◎プロジェクト作成

`WPFアプリ(.NET Framework)`（C#）でプロジェクトを作成します。
名前は`TextViewFW`にします。

### ◎NuGet

`NuGet`で高機能テキストコントロールの`AvalonEdit`をインストールします。
![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/7d8ed1e2-3851-a83b-65fc-b82f04583c90.png)

### ◎画面

![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/f61b6d9b-4483-54d5-2a96-6bb243e83b98.png)

`Window`に`xmlns:ae="http://icsharpcode.net/sharpdevelop/avalonedit"`を追加し、`Grid`の中に`ae:TextEditor`を入れます。
読み取り専用や行番号表示等のプロパティを設定します。

```Xml:TextViewFW：MainWindow.xaml
<Window x:Class="TextViewFW.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        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:local="clr-namespace:TextViewFW"
        xmlns:ae="http://icsharpcode.net/sharpdevelop/avalonedit"
        mc:Ignorable="d"
        Title="テキストビューア" Height="450" Width="800" ResizeMode="CanResizeWithGrip">
    <Grid>
        <ae:TextEditor 
            x:Name="textView" 
            IsReadOnly="True" 
            ShowLineNumbers="True"
            Drop="textView_Drop" 
            PreviewDragOver="textView_PreviewDragOver">
            <ae:TextEditor.Options>
                <!-- 改行マーク、空白表示 -->
                <ae:TextEditorOptions
                    ShowEndOfLine="True"
                    ShowSpaces="True"/>
            </ae:TextEditor.Options>

            <ae:TextEditor.ContextMenu>
                <!-- コンテキストメニュー -->
                <ContextMenu>
                    <MenuItem Header="Shift_JIS" Tag="sjis" Click="menuItem_Click"/>
                    <MenuItem Header="UTF-8" Tag="utf8" Click="menuItem_Click"/>
                </ContextMenu>
            </ae:TextEditor.ContextMenu>
        </ae:TextEditor>
    </Grid>
</Window>
```

### ◎ロジック

```CSharp:TextViewFW：MainWindow.xaml.cs
using System;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using AEHL = ICSharpCode.AvalonEdit.Highlighting;

namespace TextViewFW
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// 読み込んだファイルパス
        /// </summary>
        private string loadFilePath;

        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// ドラッグ中オブジェクトが上に来た時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void textView_PreviewDragOver(object sender, DragEventArgs e)
        {
            // マウスカーソルをコピー中に変更
            e.Effects = DragDropEffects.Copy;
            // ドラッグ中のオブジェクトがファイルの場合、受け付ける
            e.Handled = e.Data.GetDataPresent(DataFormats.FileDrop);
        }

        /// <summary>
        /// ドロップ時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void textView_Drop(object sender, DragEventArgs e)
        {
            loadFilePath = "";

            // ドロップしたファイル一覧を取得
            var files = e.Data.GetData(DataFormats.FileDrop) as string[];
            if (files == null)
            {
                textView.Clear();
                return;
            }

            // ドロップしたファイルが複数あっても1つだけ取得
            string filePath = files.First();
            try
            {
                // シンタックスハイライト設定
                textView.SyntaxHighlighting = GetSyntaxHilight(filePath);
                // ファイル読み込み
                textView.Load(filePath);
                // ファイルパスを保存しておく
                loadFilePath = filePath;
            }
            catch (NotSupportedException)
            {
                MessageBox.Show("このファイルは読み込めません。");
                return;
            }
        }

        /// <summary>
        /// シンタックスハイライト取得
        /// </summary>
        /// <param name="filePath">ファイルパス</param>
        /// <returns>シンタックスハイライト情報</returns>
        private AEHL.IHighlightingDefinition GetSyntaxHilight(string filePath)
        {
            // 拡張子取得
            string ext = System.IO.Path.GetExtension(filePath).ToLower();

            switch (ext)
            {
                case ".cs":
                    return AEHL.HighlightingManager.Instance.GetDefinition("C#");
                case ".md":
                    return AEHL.HighlightingManager.Instance.GetDefinition("MarkDown");
                case ".cpp":
                    return AEHL.HighlightingManager.Instance.GetDefinition("C++");
                case ".js":
                    return AEHL.HighlightingManager.Instance.GetDefinition("JavaScript");
                case ".json":
                    return AEHL.HighlightingManager.Instance.GetDefinition("Json");
                case ".htm":
                case ".html":
                    return AEHL.HighlightingManager.Instance.GetDefinition("HTML");
                case ".css":
                    return AEHL.HighlightingManager.Instance.GetDefinition("CSS");
                case ".xml":
                case ".xaml":
                case ".config":
                case ".csproj":
                    return AEHL.HighlightingManager.Instance.GetDefinition("XML");
                case ".ps1":
                case ".psm1":
                    return AEHL.HighlightingManager.Instance.GetDefinition("PowerShell");
                case ".java":
                    return AEHL.HighlightingManager.Instance.GetDefinition("Java");
                case ".sql":
                    return AEHL.HighlightingManager.Instance.GetDefinition("TSQL");
                case ".vb":
                    return AEHL.HighlightingManager.Instance.GetDefinition("VB");
                case ".py":
                    return AEHL.HighlightingManager.Instance.GetDefinition("Python");
                case ".php":
                    return AEHL.HighlightingManager.Instance.GetDefinition("PHP");
                case ".txt":
                case ".log":
                case ".bat":
                case ".ini":
                case ".csv":
                    return null;
                default:
                    throw new NotSupportedException();
            }
        }

        /// <summary>
        /// コンテキストメニューの項目クリック時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <remarks>エンコーディングの変更</remarks>
        private void menuItem_Click(object sender, RoutedEventArgs e)
        {
            bool changeEncoding = false;
            string tag = (sender as MenuItem).Tag.ToString();
            if (tag == "sjis")
            {
                if (textView.Encoding != Encoding.GetEncoding("Shift_JIS"))
                {
                    // エンコーディング変更
                    textView.Encoding = Encoding.GetEncoding("Shift_JIS");
                    changeEncoding = true;
                }
            }
            else
            {
                if (textView.Encoding != Encoding.UTF8)
                {
                    // エンコーディング変更
                    textView.Encoding = Encoding.UTF8;
                    changeEncoding = true;
                }
            }

            // エンコーディングが変更された？
            if (changeEncoding &&
                string.IsNullOrEmpty(loadFilePath) == false)
            {
                // 再読み込み
                textView.Load(loadFilePath);
            }
        }

    }
}
```

### ◎動かしてみる

ファイルをドラッグ＆ドロップしてみます。
![06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/5d65bbf9-4d07-68e1-fa45-96ddb30bec5f.png)

![07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/9d652d7a-3f59-3ac4-94d7-30b1d0ca3776.png)

別のファイルをドラッグ＆ドロップしてみます。
![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/76375a0a-f01a-0dea-106b-2a18a6b960f9.png)

## ◇.NET Core版

### ◎プロジェクト作成

`WPF App(.NET Core)`（C#）でプロジェクトを作成します。
名前は`TextViewCore`にします。

### ◎NuGet

`NuGet`で`AvalonEdit`をインストールするところまでは.NET Framework版と同じです。
.NET Core版はShift_JISのファイルを使えるように追加で`System.Text.Encoding.CodePages`をインストールします。
![09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/4e979fe0-9f16-88d5-38c2-8e0cfb460cc1.png)

### ◎画面

ファイルを読み込んで文字化けした時のために、コンテキストメニューでエンコーディングを変更できるようにします。

```Xml:TextViewCore：MainWindow.xaml
<Window x:Class="TextViewCore.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        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:local="clr-namespace:TextViewCore"
        xmlns:ae="http://icsharpcode.net/sharpdevelop/avalonedit"
        mc:Ignorable="d"
        Title="テキストビューア" Height="450" Width="800" ResizeMode="CanResizeWithGrip">
    <Grid>
        <ae:TextEditor 
            x:Name="textView" 
            IsReadOnly="True" 
            ShowLineNumbers="True"
            Drop="textView_Drop" 
            PreviewDragOver="textView_PreviewDragOver">
            <ae:TextEditor.Options>
                <!-- 改行マーク、空白表示 -->
                <ae:TextEditorOptions
                    ShowEndOfLine="True"
                    ShowSpaces="True"/>
            </ae:TextEditor.Options>
            
            <ae:TextEditor.ContextMenu>
                <!-- コンテキストメニュー -->
                <ContextMenu>
                    <MenuItem Header="Shift_JIS" Tag="sjis" Click="menuItem_Click"/>
                    <MenuItem Header="UTF-8" Tag="utf8" Click="menuItem_Click"/>
                </ContextMenu>
            </ae:TextEditor.ContextMenu>
        </ae:TextEditor>
    </Grid>
</Window>
```

### ◎ロジック

```CSharp:TextViewCore：MainWindow.xaml.cs
using System;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using AEHL = ICSharpCode.AvalonEdit.Highlighting;

namespace TextViewCore
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// 読み込んだファイルパス
        /// </summary>
        private string loadFilePath;

        public MainWindow()
        {
            InitializeComponent();
        }
        /// <summary>
        /// ドラッグ中オブジェクトが上に来た時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void textView_PreviewDragOver(object sender, DragEventArgs e)
        {
            // マウスカーソルをコピー中に変更
            e.Effects = DragDropEffects.Copy;
            // ドラッグ中のオブジェクトがファイルの場合、受け付ける
            e.Handled = e.Data.GetDataPresent(DataFormats.FileDrop);
        }

        /// <summary>
        /// ドロップ時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void textView_Drop(object sender, DragEventArgs e)
        {
            loadFilePath = "";

            // ドロップしたファイル一覧を取得
            var files = e.Data.GetData(DataFormats.FileDrop) as string[];
            if (files == null)
            {
                textView.Clear();
                return;
            }

            // ドロップしたファイルが複数あっても1つだけ取得
            string filePath = files.First();
            try
            {
                // シンタックスハイライト設定
                textView.SyntaxHighlighting = GetSyntaxHilight(filePath);
                // SJISも使えるようにする
                Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
                // ファイル読み込み
                textView.Load(filePath);
                // ファイルパスを保存しておく
                loadFilePath = filePath;
            }
            catch (NotSupportedException)
            {
                MessageBox.Show("このファイルは読み込めません。");
                return;
            }
        }

        /// <summary>
        /// シンタックスハイライト取得
        /// </summary>
        /// <param name="filePath">ファイルパス</param>
        /// <returns>シンタックスハイライト情報</returns>
        private AEHL.IHighlightingDefinition GetSyntaxHilight(string filePath)
        {
            // 拡張子取得
            string ext = System.IO.Path.GetExtension(filePath).ToLower();

            switch (ext)
            {
                case ".cs":
                    return AEHL.HighlightingManager.Instance.GetDefinition("C#");
                case ".md":
                    return AEHL.HighlightingManager.Instance.GetDefinition("MarkDown");
                case ".cpp":
                    return AEHL.HighlightingManager.Instance.GetDefinition("C++");
                case ".js":
                    return AEHL.HighlightingManager.Instance.GetDefinition("JavaScript");
                case ".json":
                    return AEHL.HighlightingManager.Instance.GetDefinition("Json");
                case ".htm":
                case ".html":
                    return AEHL.HighlightingManager.Instance.GetDefinition("HTML");
                case ".css":
                    return AEHL.HighlightingManager.Instance.GetDefinition("CSS");
                case ".xml":
                case ".xaml":
                case ".config":
                case ".csproj":
                    return AEHL.HighlightingManager.Instance.GetDefinition("XML");
                case ".ps1":
                case ".psm1":
                    return AEHL.HighlightingManager.Instance.GetDefinition("PowerShell");
                case ".java":
                    return AEHL.HighlightingManager.Instance.GetDefinition("Java");
                case ".sql":
                    return AEHL.HighlightingManager.Instance.GetDefinition("TSQL");
                case ".vb":
                    return AEHL.HighlightingManager.Instance.GetDefinition("VB");
                case ".py":
                    return AEHL.HighlightingManager.Instance.GetDefinition("Python");
                case ".php":
                    return AEHL.HighlightingManager.Instance.GetDefinition("PHP");
                case ".txt":
                case ".log":
                case ".bat":
                case ".ini":
                case ".csv":
                    return null;
                default:
                    throw new NotSupportedException();
            }
        }

        /// <summary>
        /// コンテキストメニューの項目クリック時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <remarks>エンコーディングの変更</remarks>
        private void menuItem_Click(object sender, RoutedEventArgs e)
        {
            bool changeEncoding = false;
            string tag = (sender as MenuItem).Tag.ToString();
            if (tag == "sjis")
            {
                // SJIS使えるようにする
                Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
                if (textView.Encoding != Encoding.GetEncoding("Shift_JIS"))
                {
                    // エンコーディング変更
                    textView.Encoding = Encoding.GetEncoding("Shift_JIS");
                    changeEncoding = true;
                }
            }
            else
            {
                if (textView.Encoding != Encoding.UTF8)
                {
                    // エンコーディング変更
                    textView.Encoding = Encoding.UTF8;
                    changeEncoding = true;
                }
            }

            // エンコーディングが変更された？
            if (changeEncoding &&
                string.IsNullOrEmpty(loadFilePath) == false)
            {
                // 再読み込み
                textView.Load(loadFilePath);
            }
        }
    }
}
```

### ◎動かしてみる

![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/e30e05db-2d61-e3ad-2e1a-e5bbf787b679.png)



おしまい

---

[＜＜ 最初の記事](http://qiita.com/Kosen-amai/items/f9e3df2aa80363f5af5b "Windows GUIプログラミング入門1 メッセージボックス表示") 　 [＜ 前の記事](https://qiita.com/Kosen-amai/items/5712dc35d7eb8b7730fe "Windows GUIプログラミング入門23 NuGet(4), トースト通知") 　 [次の記事 ＞](https://qiita.com/Kosen-amai/items/e31b62a6f892cb5a7c14 "Windows GUIプログラミング入門25 コード整形")