天天看點

WPF 如何擷取有哪些 VisualBrush 用了某個控件

我寫了一個特殊的控件,我期望了解到有哪些 VisualBrush 捕獲了此控件,或者說有哪些 VisualBrush 用了此控件的界面

本文的方法需要用到反射,需要使用 WPF 架構裡面沒有公開的字段擷取某個 Visual 控件被引用的 VisualBrush 有哪些,代碼如下

class MyUserControl : UserControl
    {
        public bool IsInVisualBrush()
        {
            return GetVisualBrushes().Any();
        }

        private List<VisualBrush> GetVisualBrushes()
        {
            var type = typeof(Visual);
            var cyclicBrushToChannelsMapField = type.GetField("CyclicBrushToChannelsMapField", BindingFlags.Static | BindingFlags.NonPublic);
            var cyclicBrushToChannelsMap = cyclicBrushToChannelsMapField.GetValue(null);

            var getValueMethod = cyclicBrushToChannelsMap.GetType().GetMethod("GetValue");
            var cyclicBrushToChannelsMapDictionary = getValueMethod.Invoke(cyclicBrushToChannelsMap, new object[] { this });
            var dictionary = cyclicBrushToChannelsMapDictionary as IDictionary;

            var visualBrushes = dictionary?.Keys.OfType<VisualBrush>().ToList() ?? new List<VisualBrush>(0);
            return visualBrushes;
        }
    }
           

通過上面代碼不僅可以擷取某個控件,是否被作為 VisualBrush 的 Visual 作為畫刷,還可以擷取目前有哪些 VisualBrush 捕獲了這個控件

寫一個簡單的界面,将這個控件設定為某個 VisualBrush 的 Visual 内容,然後将這個 VisualBrush 作為背景

<Grid x:Name="Grid">
    <Border x:Name="Border">
      <Border.Background>
        <VisualBrush Visual="{Binding ElementName=Container}"></VisualBrush>
      </Border.Background>
    </Border>

    <Grid x:Name="Container">
      <local:MyUserControl x:Name="MyUserControl"></local:MyUserControl>
    </Grid>
  </Grid>
           

在界面的構造裡面,在 InitializeComponent 方法之後,調用 IsInVisualBrush 方法,傳回的是不被 VisualBrush 捕獲。但是如果在 Loaded 事件擷取,傳回的是沒有被捕獲。隻有在 Loaded 事件加上 Dispatcher 延遲傳回的才是被捕獲

public MainWindow()
        {
            InitializeComponent();

            Loaded += MainWindow_Loaded;
        }

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            Dispatcher.InvokeAsync(() =>
            {
                MyUserControl.IsInVisualBrush();
            });
        }
           

而如果在點選按鈕的時候,将使用了 VisualBrush 作為背景的 Border 移除,那麼立刻就可以擷取到沒有被捕獲

private void Button_OnClick(object sender, RoutedEventArgs e)
        {
            Grid.Children.Remove(Border);

            MyUserControl.IsInVisualBrush(); // 傳回 false 沒有被捕獲
        }
           

上面代碼其實用到了 WPF 的機制,在 WPF 裡面,所有的控件都繼承了 Visual 類型(無視3D部分)而在此類型裡面,将會在被 VisualBrush 使用的時候,調用 AddRefOnChannelForCyclicBrush 方法

internal virtual void AddRefOnChannelForCyclicBrush(
            ICyclicBrush cyclicBrush,
            DUCE.Channel channel)
        {
            // 忽略代碼

            Dictionary<ICyclicBrush, int> cyclicBrushToChannelsMap =
                CyclicBrushToChannelsMapField.GetValue(this);

            if (cyclicBrushToChannelsMap == null)
            {
                cyclicBrushToChannelsMap = new Dictionary<ICyclicBrush, int>();
                CyclicBrushToChannelsMapField.SetValue(this, cyclicBrushToChannelsMap);
            }

            if (!cyclicBrushToChannelsMap.ContainsKey(cyclicBrush))
            {
                cyclicBrushToChannelsMap[cyclicBrush] = 1;
            }
            else
            {
                Debug.Assert(cyclicBrushToChannelsMap[cyclicBrush] > 0);

                cyclicBrushToChannelsMap[cyclicBrush] += 1;
            }

            //
            // Render the brush's visual.
            //

            cyclicBrush.RenderForCyclicBrush(channel, false);
        }
           

上面的 ICyclicBrush 定義如下

internal interface ICyclicBrush
    {
        void FireOnChanged();

        void RenderForCyclicBrush(DUCE.Channel channel, bool skipChannelCheck);
    }
           

所有 VisualBrush 繼承了這個接口

public sealed partial class VisualBrush : TileBrush, ICyclicBrush
  {

  }
           

在設定 VisualBrush 的 Visual 屬性的時候,将會觸發 VisualPropertyChanged 函數

static VisualBrush()
        {
            // Initializations
            Type typeofThis = typeof(VisualBrush);
            VisualProperty =
                  RegisterProperty("Visual",
                                   typeof(Visual),
                                   typeofThis,
                                   null,
                                   new PropertyChangedCallback(VisualPropertyChanged),
                                   null,
                                   /* isIndependentlyAnimated  = */ false,
                                   /* coerceValueCallback */ null);
        }

        private static void VisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {

        }
           

在這個函數裡面将會調用 VisualBrush 的 AddRefResource 方法

public sealed partial class VisualBrush : TileBrush, ICyclicBrush
    {
        private static void VisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
        	// 忽略代碼
            System.Windows.Threading.Dispatcher dispatcher = target.Dispatcher;

            if (dispatcher != null)
            {
                DUCE.IResource targetResource = (DUCE.IResource)target;
                using (CompositionEngineLock.Acquire())
                {
                    int channelCount = targetResource.GetChannelCount();

                    for (int channelIndex = 0; channelIndex < channelCount; channelIndex++)
                    {
                        DUCE.Channel channel = targetResource.GetChannel(channelIndex);
                        Debug.Assert(!channel.IsOutOfBandChannel);
                        Debug.Assert(!targetResource.GetHandle(channel).IsNull);
                        target.ReleaseResource(oldV,channel);
                        target.AddRefResource(newV,channel);
                    }
                }
            }

            target.PropertyChanged(VisualProperty);
        }
    }
           

在 AddRefResource 函數裡面将會調用上文的具體的 Visual 的 AddRefOnChannelForCyclicBrush 方法

public sealed partial class VisualBrush : TileBrush, ICyclicBrush
    {
        internal void AddRefResource(Visual visual, DUCE.Channel channel)
        {
            if (visual != null)
            {
                visual.AddRefOnChannelForCyclicBrush(this, channel);
            }
        }
    }
           

是以在 Visual 裡面是可以了解到目前的的對象被哪些 VisualBrush 捕獲

而在 Visual 裡面存放的字典是不開放的,需要使用本文的反射的方式才能拿到對象進而了解這個控件是否作為 VisualBrush 的内容

本文所有代碼放在 github 和 gitee 歡迎通路

可以通過如下方式擷取本文的源代碼,先建立一個空檔案夾,接着使用指令行 cd 指令進入此空檔案夾,在指令行裡面輸入以下代碼,即可擷取到本文的代碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 04a3f64cc878d8e4890e72877ff08e473b4fc1a8
           

以上使用的是 gitee 的源,如果 gitee 不能通路,請替換為 github 的源

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
           

擷取代碼之後,進入 CalbuhewaNallrolayrani 檔案夾

參考:c# - How to know whether my control be used in VisualBrush - Stack Overflow

部落格園部落格隻做備份,部落格釋出就不再更新,如果想看最新部落格,請到 https://blog.lindexi.com/

WPF 如何擷取有哪些 VisualBrush 用了某個控件

本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協定進行許可。歡迎轉載、使用、重新釋出,但務必保留文章署名[林德熙](http://blog.csdn.net/lindexi_gd)(包含連結:http://blog.csdn.net/lindexi_gd ),不得用于商業目的,基于本文修改後的作品務必以相同的許可釋出。如有任何疑問,請與我[聯系](mailto:[email protected])。