頼りないニモニック

はっきりいって個人の日記レベル

文字そのものがクリックされたかを知る

研究で急遽必要になったため久々に WPF をやっています。

今回、TextBlock の文字 そのもの をクリックしたいと思ったのですが、結構はまってしまいました。


f:id:nanase_t:20131107002808p:plainf:id:nanase_t:20131107002915p:plain

private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
    TextBlock textblock = (TextBlock)sender;
    textblock.Foreground = Brushes.Magenta;
}

private void TextBlock_MouseUp(object sender, MouseButtonEventArgs e)
{
    TextBlock textblock = (TextBlock)sender;
    textblock.Foreground = Brushes.Black;
}

やっていることは TextBlock でマウスダウンしたら色を変え、マウスアップしたら元に戻す、ただそれだけです。
ところがこれだと TextBlock のコントロールをクリックしたことになり、実際に文字の描画されていない透過色の部分をクリックしても反応してしまいます。やりたいことは、文字そのものがクリックされているかどうかなので、そのためにはクリックされたときのマウスカーソルが、文字の真上にあるかどうかを判断できればいいわけです。

解決法

数時間調べまわってやっとこさ見つけました。

どうやらコントロールの構成要素である Drawing を再帰的にチェックし、最終的には Geometry.FillContains メソッドで判定するようです。
MSDN のサンプルだと TextBlock に対して直接適用できないため、手直し + 真偽値で判定するようにアレンジしてみました。

private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
    TextBlock textblock = (TextBlock)sender;
    Point pt = e.GetPosition(textblock);

    if (HitTestGeometryInVisual(textblock, pt))
        textblock.Foreground = Brushes.Magenta;
}

private void TextBlock_MouseUp(object sender, MouseButtonEventArgs e)
{
    TextBlock textblock = (TextBlock)sender;
    textblock.Foreground = Brushes.Black;
}

// Refer to : http://msdn.microsoft.com/en-us/library/system.windows.media.visualtreehelper.getdrawing.aspx
static public bool HitTestGeometryInVisual(Visual visual, Point pt)
{
    DrawingGroup drawingGroup = VisualTreeHelper.GetDrawing(visual);
    return EnumDrawingGroup(drawingGroup, pt);
}

static public bool EnumDrawingGroup(DrawingGroup drawingGroup, Point pt)
{
    DrawingCollection drawingCollection = drawingGroup.Children;

    foreach (Drawing drawing in drawingCollection)
    {
        if (drawing is DrawingGroup)
        {
            if (EnumDrawingGroup((DrawingGroup)drawing, pt))
                return true;
        }
        else if (drawing is GeometryDrawing)
        {
            if (((GeometryDrawing)drawing).Geometry.FillContains(pt))
                return true;
        }
        else if (drawing is GlyphRunDrawing)
        {
            if (((GlyphRunDrawing)drawing).GlyphRun.BuildGeometry().FillContains(pt))
                return true;
        }
    }

    return false;
}

TextBlock の場合、構成要素に GlyphRunDrawing がありますが Geometry は持っていないため BuildGeometry を呼び出してから判定しています。