我寫了一個特殊的控制元件,我期望了解到有哪些 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/

本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可,歡迎轉載、使用、重新發布,但務必保留文章署名[林德熙](http://blog.csdn.net/lindexi_gd)(包含鏈接:http://blog.csdn.net/lindexi_gd ),不得用于商業目的,基于本文修改后的作品務必以相同的許可發布,如有任何疑問,請與我[聯系](mailto:[email protected]),
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/295799.html
標籤:.NET技术
