連絡先 Hikwareホーム
Hikware.Tech
自分用の覚え書きをそのまま公開。参考程度にどうぞ。

ブラウザからのドラッグ&ドロップを受け取る

公開日 2017/08/20
最終更新 2017/08/30
  • ブラウザのURL欄やリンクからのドラッグ&ドロップを受け取り、URLとページタイトルを取得する。

ドロップ時に送られてくるデータ形式

 とりあえず、現状使っているブラウザのURL欄からのドラッグ&ドロップを受け取って、取得可能なデータ形式をログ出力してみた(2017.08現在)。

  • Firefox
    * FileContents
    * FileGroupDescriptor
    * FileGroupDescriptorW
    * UniformResourceLocator
    * UniformResourceLocatorW
    * System.String
    * UnicodeText
    * Text
      application/x-moz-custom-clipdata
      text/html
      text/x-moz-url
      HTML Format
      DragContext
      DragImageBits
    
  • Chrome
    * FileContents
    * FileGroupDescriptorW
    * UniformResourceLocator
    * UniformResourceLocatorW
    * System.String
    * UnicodeText
    * Text
      text/x-moz-url
      DragContext
      DragImageBits
    
  • IE11
    * FileContents
    * FileGroupDescriptor
    * FileGroupDescriptorW
    * UniformResourceLocator
    * UniformResourceLocatorW
    * System.String
    * UnicodeText
    * Text
      Shell IDList Array
      IESiteModeToUrl
    

 * を付けたデータは、少なくとも上記3ブラウザでは共通。ブラウザからドラッグされたURLデータであるか否かは UniformResourceLocatorUniformResourceLocatorW の有無で判別するのが定石らしい。URLは Text で簡単に取得できる。

// ドラッグ物がフォーム上に入った際のイベント
private void Form_DragEnter( object sender, DragEventArgs e )
{
    // ブラウザからのURLデータなら受け入れる
    if ( e.Data.GetDataPresent( "UniformResourceLocator" ) ||
         e.Data.GetDataPresent( "UniformResourceLocatorW" ) )
        { e.Effect = DragDropEffects.Link; }
    else
        { e.Effect = DragDropEffects.None; }
}

// ドロップ時イベント
private void Form_DragDrop( object sender, DragEventArgs e )
{
    if ( e.Data.GetDataPresent( "UniformResourceLocator" ) ||
         e.Data.GetDataPresent( "UniformResourceLocatorW" ) )
    {
        // 上記形式で GetData すると変換が面倒なので、Text で取ると楽
        if ( e.Data.GetDataPresent( DataFormats.Text ) )
            { var url = e.Data.GetData( DataFormats.Text ) as string; }
    }
}

 問題はページタイトルで、上記データの内容をそれぞれ実際に GetData() で取得してみたところ、ページタイトルを含むのは FileGroupDescriptor(W)text/x-moz-url くらい。

形式 C#の型 内容 タイトル
FileContents MemoryStream ショートカットファイル
の内容(ANSI文字列)
×
FileGroupDescriptor MemoryStream ショートカットファイル名
(FILEGROUPDESCRIPTOR)
含む
FileGroupDescriptorW MemoryStream ショートカットファイル名
(FILEGROUPDESCRIPTOR)
含む
UniformResourceLocator MemoryStream URL (ANSI文字列) ×
UniformResourceLocatorW MemoryStream URL (UTF16文字列) ×
System.String string URL ×
UnicodeText string URL ×
Text string URL ×
HTML Format string リンク部分のHTML情報
text/html MemoryStream リンク部分のHTML
(UTF16文字列)
text/x-moz-url MemoryStream Firefox独自のURL情報
(UTF16文字列)
含む

 ちなみに、ブラウザのURL欄からドラッグした場合には表示中のページタイトルが取得できるが、ページ内のリンクをドラッグした場合は、そのリンク部分の文言が取得できるだけで、リンク先のページタイトルを取得できる訳ではない

 表の△マークについては、ページ内のリンクをドラッグした場合にはリンク部分の文言が取得できるが、URL欄からドラッグした場合にはページタイトルは取得できず、使えない。

FileGroupDescriptor(W) からページタイトル取得

 URLのドラッグデータには、インターネットショートカットを作るために、ショートカットファイル(.url形式)の内容そのもの FileContents と、ファイル名情報 FileGroupDescriptor(W) が添付されてる。

 ショートカットファイルの内容にはページタイトルは含まれてないが、ファイル名がほぼページタイトルなのでそれを取得する。ただし ファイル名である以上、ファイル名として使えない文字は省かれてるし、長さ制限もあるので、完全なページタイトルではない

 FileGroupDescriptor(W) の内容は Win32API の FILEGROUPDESCRIPTOR 構造体だが、C# では直接アクセスできないので、構造体内のファイル名の部分、先頭から76Byte目より後を直接取得する。正直キモイが仕方なし。

 ファイル名は FileGroupDescriptor なら デフォルトのANSI文字コード(※UTF-8ではない)FileGroupDescriptorW なら UTF16 で格納されている。ANSI文字コードでは自国語以外のページタイトルを正常に扱えないので、FileGroupDescriptorW を優先的に取得する。

// DragEventArgs.Data からURLとページタイトル取得
public bool
GetUrlInfo(
    IDataObject data,
    out string url,
    out string title)
{
    url = title = null;

    if ( data.GetDataPresent( DataFormats.Text ) )
        { url = data.GetData( DataFormats.Text ) as string; }
    if ( url == null )
        { return false; }

    // Unicode の FileGroupDescriptorW から優先的に取得
    return (
        GetTitle( data, out title, "FileGroupDescriptorW", Encoding.Unicode ) ||
        GetTitle( data, out title, "FileGroupDescriptor", Encoding.Default ) );
}

private bool
GetTitle(
    IDataObject data,
    out string title,
    string dataFormat,
    Encoding encoding)
{
    title = null;

    // FILEGROUPDESCRIPTOR が MemoryStream で格納されてる
    if ( !data.GetDataPresent( dataFormat ) )
        { return false; }
    var stream = data.GetData( dataFormat ) as System.IO.MemoryStream;
    if ( (stream == null) || !stream.CanSeek )
        { return false; }

    // FILEDESCRIPTOR[0].cFileName まで移動
    var offsetFileName = 76;
    if ( stream.Length < offsetFileName )
        { return false; }
    stream.Seek( offsetFileName, IO.SeekOrigin.Current );

    // とりあえず全部文字列化
    var buffer = new byte[stream.Length - offsetFileName];
    stream.Read( buffer, 0, buffer.Length );
    title = encoding.GetString( buffer );

    // 末尾のヌル文字と拡張子を排除
    var term = title.Length;
    for ( int i = 0; i < title.Length; i++ )
    {
        if ( title[i] == '\0' )
        {
            term = Math.Min( term, i );
            break;
        }
        else if ( title[i] == '.' )
            { term = i; }
    }                
    title = title.Substring( 0, term );

    return true;
}

text/x-moz-url からページタイトル取得

 Firefox からのURLのドラッグデータには、独自のデータ形式 text/x-moz-url が含まれている。内容は UTF16 の文字列で、1行目がURL、2行目がページタイトル。

 FileGroupDescriptor(W) とは違って完全なページタイトルを取得できるし、なんでか Chrome でも採用されてるので、可能ならこちらからページタイトルを取得した方が正確。

// DragEventArgs.Data からURLとページタイトル取得
public bool
GetUrlInfo(
    IDataObject data,
    out string url,
    out string title)
{
    url = title = null;

    if ( data.GetDataPresent( DataFormats.Text ) )
        { url = data.GetData( DataFormats.Text ) as string; }
    if ( url == null )
        { return false; }

    if ( data.GetDataPresent( "text/x-moz-url" ) )
    {
        var stream = data.GetData( "text/x-moz-url" ) as IO.MemoryStream;
        if ( stream != null )
        {
            // UTF16で文字列化
            var buffer = new byte[stream.Length];
            stream.Read( buffer, 0, buffer.Length );
            var text = Encoding.Unicode.GetString( buffer );

            // 末尾にヌル文字が付いてるので行分割のついでに省く
            var lines = text.Split( new char[] { '\n', '\r', '\0' },
                                    StringSplitOptions.RemoveEmptyEntries );
            // 一応、URLの整合チェック
            if ( (lines.Length >= 2) && (lines[0] == url) )
            {
                title = lines[1];
                return true;
            }
        }
    }
    return false;
}