我寫了一個特殊的控件,我期望了解到有哪些 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 的內容
可以通過如下方式獲取本文的源代碼,先創建一個空文件夾,接着使用命令行 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