天天看點

WPF技巧:命中測試在視覺樹中的使用

  我們有時候又需求從目前視覺樹中找一些東西,比如滑鼠按下的時候,看看滑鼠下的元素都有什麼。又比如某塊區域下有哪些元素?某個坐标點下有哪些元素?

  這些需求在使用 命中測試的時候,可以非常友善和快速的去找到我們需要的内容。

簡單命中測試

  我們寫一個最簡單的命中測試的示例,來了解命中測試。我在一個畫闆上在不同的位置放了3個圓形。給他們放置了不同的位置和填充不同的顔色,我們通過命中測試判斷如果滑鼠在圓上擡起了,我們讀取目前圓的填充顔色。 

<Window x:Class="WPFVisualTreeHelper.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:WPFVisualTreeHelper"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid MouseLeftButtonUp="Grid_MouseLeftButtonUp">
        <Canvas>
            <Ellipse  Canvas.Left="30" Canvas.Top="200"  Width="130" Height="130" Fill="Blue"/>
            <Ellipse  Canvas.Left="110" Canvas.Top="0"  Width="130" Height="130" Fill="Red"/>
            <Ellipse   Canvas.Left="220" Canvas.Top="100"  Width="130" Height="130" Fill="Yellow"/>
            <TextBlock  Canvas.Left="0" Canvas.Top="0" Text="擡起滑鼠左鍵,開始對滑鼠所在點進行命中測試" />
        </Canvas>
          </Grid>
</Window>      

我們給Grid 添加了左鍵擡起的事件。

using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;

namespace WPFVisualTreeHelper
{
    /// <summary>
    /// MainWindow.xaml 的互動邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            var ellipse = GetVisual(e.GetPosition(this));
              ;
            MessageBox.Show(ellipse?.Fill?.ToString());
        }

        private Ellipse GetVisual(Point point)
        {
            HitTestResult hitResult = VisualTreeHelper.HitTest(this, point);
            var ellipse = hitResult.VisualHit as Ellipse;
            return ellipse;
        }
    }
}      

如果在圓上擡起,我們會顯示圓的填充色。

WPF技巧:命中測試在視覺樹中的使用

我們簡單命中測試使用VisualTreeHelper來進行命中測試。HitTest方法傳入2個參數,一個參數在什麼元素上查找,第二個參數是坐标。傳回的對象是命中測試的傳回結果,是DependencyObject類型的對象。我們的ui元素都是繼承自DependencyObject的。找到的結果是HitTestResult,需要我們自己轉換VisualHit到我們需要的類型,我們的方法如果不是該類型就傳回為空。因為是簡單命中測試。後面會封裝一個命中測試的方法。因為在命中測試過程中,會有元素的層級關系,多個同類型元素集合,等等的需求。是以這裡隻了解什麼是命中測試。能看懂代碼就可以了,了解什麼是命中測試。就可以了。

複雜命中測試

我們通過在VisualTreeHelper的HitTest上F12我們可以看到有3個方法的重載。

WPF技巧:命中測試在視覺樹中的使用

 VisualTreeHelper類使用其他重載版本可以執行更複雜的命中測試。我們可以檢索位于特定點的所有可視化對象,也可以使用特定區域的所有可視化對象。我們使用第一個重載方法。

  因為在視覺樹下,有層級和同級多個元素的問題。為了使用這個功能,我們需要建立回調函數,也就是第一個HitTest的第三個參數resultCallback。我們通過自上而下周遊所有可視對象,如果發現了比對的對象就使用回調函數傳遞相關的内容直到找到所有的對象。

WPF技巧:命中測試在視覺樹中的使用

  我們寫個例子,我們在上面的例子上多添加幾個圓形,并且把他們疊加起來,然後我們建立一個10*10像素的形狀,去檢索這個形狀坐标下的所有圓形。為了讓他們顯示層疊關系我設定了opacity屬性。并使用滑鼠右鍵來執行複雜命中測試。 <Window x:Class="WPFVisualTreeHelper.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:WPFVisualTreeHelper"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid  MouseLeftButtonUp="Grid_MouseLeftButtonUp"   MouseRightButtonUp="Grid_MouseRightButtonUp">
        <Canvas> 
            <Ellipse Canvas.Left="30" Canvas.Top="200"  Width="130" Height="130" Fill="Blue"/>
            <Ellipse  Opacity="0.6" Canvas.Left="70" Canvas.Top="50"  Width="130" Height="130" Fill="Violet"/>
            <Ellipse  Opacity="0.6"   Canvas.Left="150" Canvas.Top="50"  Width="130" Height="130" Fill="Orange"/>
            <Ellipse  Opacity="0.6"  Canvas.Left="110" Canvas.Top="0"  Width="130" Height="130" Fill="Red"/>
            <Ellipse  Canvas.Left="220" Canvas.Top="100"  Width="130" Height="130" Fill="Yellow"/>
            <TextBlock  Canvas.Left="0" Canvas.Top="0" Text="擡起滑鼠左鍵,開始對滑鼠所在點進行命中測試" />
        </Canvas>
          </Grid>
</Window>      

背景代碼:

using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;

namespace WPFVisualTreeHelper
{
    /// <summary>
    /// MainWindow.xaml 的互動邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DrawingVisual v = new DrawingVisual();

        }
        #region 簡單命中測試
        private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            var ellipse = GetVisual(e.GetPosition(this));
            ;
            MessageBox.Show(ellipse?.Fill?.ToString());
        }
        private Ellipse GetVisual(Point point)
        {
            HitTestResult hitResult = VisualTreeHelper.HitTest(this, point);
            var ellipse = hitResult.VisualHit as Ellipse;
            return ellipse;
        }
        #endregion

        #region 複雜命中測試 
        private void Grid_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
        {
            Point pt = e.GetPosition((UIElement)sender);
            //我們定義一個10*10大小的幾何
            EllipseGeometry expandedHitTestArea = new EllipseGeometry(pt, 10.0, 10.0);
            var ellipses = GetVisual(expandedHitTestArea);
            StringBuilder stringBuilder = new StringBuilder();
            foreach (var item in ellipses)
            {
                stringBuilder.Append(item.Fill.ToString() + ",");
            }
            MessageBox.Show(stringBuilder.ToString());
        }


        private HitTestResultBehavior HitTestCallback(HitTestResult result)
        {
            GeometryHitTestResult geometryResult = (GeometryHitTestResult)result;
            Ellipse visual = result.VisualHit as Ellipse;
            if (visual != null)
            {
                hits.Add(visual);
            }
            return HitTestResultBehavior.Continue;
        }
        List<Ellipse> hits = new List<Ellipse>();
        private List<Ellipse> GetVisual(Geometry region)
        {
            hits.Clear();
            GeometryHitTestParameters parameters = new GeometryHitTestParameters(region);
            HitTestResultCallback callback = new HitTestResultCallback(this.HitTestCallback);
            //第一個參數是我們要在什麼容器内查找(我們現在是在整個window),第二個參數是篩選回調值的方法,我們目前不需要,
            //第三個參數是命中測試回調結果。第四個參數是需要檢測的區域。
            VisualTreeHelper.HitTest(this, null, callback, parameters);
            return hits;
        }
        #endregion


    }
}      
HitTestCallback就是我們基于搜尋結果回調的關鍵方法。我們查找了這個幾何圖形下的所有ellispe。       

命中測試的封裝

這裡搞錯了,這裡封裝的是查找元素的封裝。。這個在補完C#的知識文章後更改。  

我建立了一個C#相關的交流群。用于分享學習資料和讨論問題。歡迎有興趣的小夥伴:QQ群:542633085