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;
        }
    }
}

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

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

復雜命中測試

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

 

 

 

 

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

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

  我們寫個例子,我們在上面的例子上多添加幾個圓形,並且把他們疊加起來,然后我們創建一個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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM