5
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-15

■はじめに

今回はエクスプローラーからファイルのドラッグ&ドロップを処理します。
最初にドロップされたファイルのフルパス一覧を表示するサンプルを作った後、
ドラッグ&ドロップでテキストファイルの内容を表示するビューアを作成します。
00_2.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

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

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>

◇ロジック

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

ファイルのフルパスが表示されました。
03.png

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

■テキストビューア

◇.NET Framework版

◎プロジェクト作成

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

◎NuGet

NuGetで高機能テキストコントロールのAvalonEditをインストールします。
04.png

◎画面

05.png

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

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>

◎ロジック

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

07.png

別のファイルをドラッグ&ドロップしてみます。
08.png

◇.NET Core版

◎プロジェクト作成

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

◎NuGet

NuGetAvalonEditをインストールするところまでは.NET Framework版と同じです。
.NET Core版はShift_JISのファイルを使えるように追加でSystem.Text.Encoding.CodePagesをインストールします。
09.png

◎画面

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

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>

◎ロジック

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

おしまい


<< 最初の記事   < 前の記事   次の記事 >

5
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?