首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >WPF 使用 MarkupExtension 实现更灵活的属性赋值与控制

WPF 使用 MarkupExtension 实现更灵活的属性赋值与控制

作者头像
jgrass
发布2024-12-25 16:47:51
发布2024-12-25 16:47:51
5490
举报
文章被收录于专栏:蔻丁杂记蔻丁杂记

原始需求

一个菜单项(MenuItem)有多个子菜单,如果所有子菜单都不可见,则父菜单也隐藏。

一个直接的实现思路是,使用 MultiBinding,将父菜单的 Visibility 属性,绑定到所有子菜单上。但这种写法,在子菜单变更时,需要手动修改代码,而且其它业务也需要这个功能时,难以直接复用。

使用 MarkupExtension 的实现方式

代码语言:javascript
复制
/// <summary>/// 父菜单是否可见,由全部的子菜单决定;如果所有的子菜单都不可见,则父菜单不可见/// </summary>internal class ParentMenuItemVisibilityConverter : MarkupExtension{    public override object ProvideValue(IServiceProvider serviceProvider)    {        var service = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;        var targetProperty = service?.TargetProperty as DependencyProperty;        var targetObject = service?.TargetObject;
        if (targetObject is MenuItem menu && targetProperty != null)        {            // 在父菜单 Loaded 时,检查所有子菜单的可见性,决定父菜单的可见性            menu.Loaded += (sender, args) =>            {                menu.Visibility = CheckParentVisibility(menu);            };            return Visibility.Visible;
        }        else        {            return DependencyProperty.UnsetValue;        }    }
    private Visibility CheckParentVisibility(MenuItem? parentMenu)    {        if (parentMenu is { } menu)        {            var menuItems = menu.Items;            foreach (var itemItem in menuItems)            {                if (itemItem is MenuItem item)                {                    if (item.Visibility == Visibility.Visible)                    {                        // 只要有一个子菜单可见,则父菜单项课件                        return Visibility.Visible;                    }                }            }        }
        return Visibility.Collapsed;    }

使用:

代码语言:javascript
复制
<MenuItem Header="帮助"          x:Name="HelpMenuItem"          Visibility="{local:ParentMenuItemVisibilityConverter}">
    <MenuItem Header="帮助1">    </MenuItem>    <MenuItem Header="帮助2">    </MenuItem>    <MenuItem Header="https://blog.jgrass.cc"/></MenuItem>

简单来说就是,在 MarkupExtension 的实现中,可以拿到 父菜单 的实例,可以订阅其 Loaded 事件,在这里更新 Visibility 属性。

重点说明

使用 MarkupExtension 的好处时,里面可以拿到操作的实例,属性等上下文信息,而如果只是写普通的 Converter,有些数据拿不到,使用 MarkupExtension 更灵活。

但另一方面,需要根据自己的业务逻辑,确定具体的实现方式,上面使用 Loaded 事件可以处理,但在有些业务场景下,就不一定适用了。

其它玩法

在 MarkupExtension.ProvideValue 中,除了返回属性对应的值,还可以返回 Binding,相当于在 XAML 中直接写 Binding,但好处是,这里可以拿到更多的上下文信息,Binging 可以非常灵活的执行。

下面这里例子,就是一个更复杂的写法(实际中没有必要)。

这里返回了一个 Binding,而此 Binding 有一个 Converter,这个 Converter,就可以拿到很多直接在 XAML 写拿不到的数据(比如父菜单本身,直接在 XAML 拿会造成循环引用)。

代码语言:javascript
复制
internal class ParentMenuItemVisibilityConverter : MarkupExtension, IValueConverter{
    public MenuItem? MenuItem { get; set; }
    public Binding? Binding { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)    {        return CheckParentVisibility(MenuItem);    }
    private void ItemOnLoaded(object sender, RoutedEventArgs e)    {        // 手动通过绑定更新值        MenuItem?.GetBindingExpression(UIElement.VisibilityProperty)?.UpdateTarget();    }
    private void ItemOnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)    {        // 手动通过绑定更新值        MenuItem?.GetBindingExpression(UIElement.VisibilityProperty)?.UpdateTarget();    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)    {        throw new NotSupportedException();    }
    public override object ProvideValue(IServiceProvider serviceProvider)    {        var service = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;        var targetProperty = service?.TargetProperty as DependencyProperty;        var targetObject = service?.TargetObject;
        if (targetObject is MenuItem menu && targetProperty != null)        {
            var binding = new Binding            {                Source = menu,                Path = new PropertyPath("Items.Count"),                Converter = this,            };
            this.MenuItem = menu;            this.Binding = binding;
            BindingOperations.SetBinding(menu, targetProperty, binding);            return binding.ProvideValue(serviceProvider); // 返回一个 Binding
            ////menu.Loaded += (sender, args) =>            ////{            ////    menu.Visibility = CheckParentVisibility(menu);            ////};
            ////return Visibility.Visible;
        }        else        {            throw new InvalidOperationException("ParentMenuItemVisibilityConverter 只能用于 MenuItem 的 Visibility 属性");        }    }
    private Visibility CheckParentVisibility(MenuItem? menu1)    {        if (menu1 is { } menu)        {            var menuItems = menu.Items;            foreach (var itemItem in menuItems)            {                if (itemItem is MenuItem item)                {                    item.IsVisibleChanged -= ItemOnIsVisibleChanged;                    item.IsVisibleChanged += ItemOnIsVisibleChanged;                    item.Loaded -= ItemOnLoaded;                    item.Loaded += ItemOnLoaded;
                    if (item.Visibility == Visibility.Visible)                    {                        return Visibility.Visible;                    }                }            }        }
        return Visibility.Collapsed;    }}

总结

MarkupExtension 用来可以比较灵活,毕竟 Binding 的基类就是 MarkupExtension,灵活也会带来问题,处理不好可能会引入内存泄漏(事件订阅那里),重复执行等问题。

参考文章

Markup Extensions and XAML - WPF .NET Framework | Microsoft Learn

WPF 中自定义 MarkupExtension - Hello—— 寻梦者! - 博客园

如何编写 WPF 的标记扩展 MarkupExtension,即便在 ControlTemplate/DataTemplate 中也能生效_walter lv 的博客 - CSDN 博客


原文链接: https://cloud.tencent.com/developer/article/2481521

本作品采用 「署名 4.0 国际」 许可协议进行许可,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年10月13日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 原始需求
  • 使用 MarkupExtension 的实现方式
  • 重点说明
  • 其它玩法
  • 总结
  • 参考文章
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档