Winui 3中的样式二传手绑定

发布于 2025-02-13 18:20:09 字数 643 浏览 0 评论 0原文

winui 3是否支持样式设置器中的绑定?我为导航视图定义了一种样式,第三行是:这是:

<Setter Property="CompactPaneLength" Value="{Binding CurrentCompactPaneLength}" />

这会产生指定的铸件无效。在运行时例外。包含NavigationView的页面的DataContext是页面的ViewModel。 navigationview.compactpanelength和currentCompactpaneLength是双重和公共和CurrentCompactpaneLength是一个可观察的Obibject(来自communityToolkit.mvvm.componentmodel)。

WINUI 3(SDK 1.1.2)的源代码包括各种设置器,例如

<Setter Target="PaneContentGrid.Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=CompactPaneLength}" />

在代码工作中进行绑定,如果是必要的,则进行了绑定。但是XAML也不工作吗?

Does WinUI 3 support binding in a Style Setter? I've defined a Style for a NavigationView and the third line is:

<Setter Property="CompactPaneLength" Value="{Binding CurrentCompactPaneLength}" />

This produces a Specified cast is not valid. exception at run time. The DataContext for the page containing the NavigationView is the ViewModel for the page. Both NavigationView.CompactPaneLength and CurrentCompactPaneLength are double and public and CurrentCompactPaneLength is an ObservableObject (from CommunityToolkit.Mvvm.ComponentModel).

The source code for the WinUI 3 (SDK 1.1.2) includes various Setters, such as

<Setter Target="PaneContentGrid.Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=CompactPaneLength}" />

Doing the bindings in code works, if that's what's necessary. But shouldn't the XAML work, too?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

清欢 2025-02-20 18:20:11

显然,在Winui 3中尚不支持二传手中的一般绑定,尽管这是一个备受要求的功能。解决方法是创建一个带有依赖关系的助手类,每当属性被更改/设置时,它都会调用变更处理程序。然后,更改处理程序可以在代码中创建所需的绑定。对 clemens 建议UWP以前的建议。这是一个示例助手类:

internal class BindingHelper
{
    #region CompactPaneLengthBindingPath
    public static readonly DependencyProperty CompactPaneLengthBindingPathProperty = DependencyProperty.RegisterAttached(
            "CompactPaneLengthBindingPath", typeof(string), typeof(BindingHelper), new PropertyMetadata(null, BindingPathChanged));
            
    public static string GetCompactPaneLengthBindingPath(DependencyObject obj)
    {
        return (string)obj.GetValue(CompactPaneLengthBindingPathProperty);
    }
    
    public static void SetCompactPaneLengthBindingPath(DependencyObject obj, string value)
    {
        obj.SetValue(CompactPaneLengthBindingPathProperty, value);
    }
    #endregion
        
    #region HeightBindingPath
    // another DP is defined here (all of them are strings)
        
    #region ForegroundBindingPath
    // and a third one, etc.
        

    // ===================== Change Handler: Creates the actual binding

    private static void BindingPathChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue is string source)                                                                // source property (could add DataContext by setting Value="source@datacontext" for example)
        {
            DependencyProperty target;                                                                  // which property is the target of the binding?
            if (e.Property == CompactPaneLengthBindingPathProperty) target = NavigationView.CompactPaneLengthProperty;
            else if (e.Property == HeightBindingPathProperty) target = FrameworkElement.HeightProperty;
            else if (e.Property == ForegroundBindingPathProperty) target = Control.ForegroundProperty;
            else throw new System.Exception($"BindingHelper: Unknown target ({nameof(e.Property)}");    // don't know this property
        
            obj.ClearValue(target);                                                                     // clear previous bindings (and value)
            BindingOperations.SetBinding(obj, target,                                                   // set new binding (and value)
               new Binding { Path = new PropertyPath(source), Mode = BindingMode.OneWay });
        }
    }

请注意,所有依赖关系均为字符串类型,目标类型可以是您正在使用的控件的任何祖先类型。例如,heightbindingpathproperty可以与任何Frameworkelement一起使用。

像这样的二阶,使用样式的助手,这样:

<Style x:Key="MyNavigationView" TargetType="controls:NavigationView" >
    <Setter Property="local:BindingHelper.CompactPaneLengthBindingPath" Value="CurrentCompactPaneLength" />
</Style>

我希望这会有所帮助。

Apparently, general bindings in Setters are not supported yet in WinUI 3, although it is a much-requested feature. A workaround is to create a helper class with a DependencyProperty in it that calls a change handler whenever the property is changed/set. The change handler can then create the required binding in code. Kudos to clemens who suggested something like this ages ago for UWP. Here is an example helper class:

internal class BindingHelper
{
    #region CompactPaneLengthBindingPath
    public static readonly DependencyProperty CompactPaneLengthBindingPathProperty = DependencyProperty.RegisterAttached(
            "CompactPaneLengthBindingPath", typeof(string), typeof(BindingHelper), new PropertyMetadata(null, BindingPathChanged));
            
    public static string GetCompactPaneLengthBindingPath(DependencyObject obj)
    {
        return (string)obj.GetValue(CompactPaneLengthBindingPathProperty);
    }
    
    public static void SetCompactPaneLengthBindingPath(DependencyObject obj, string value)
    {
        obj.SetValue(CompactPaneLengthBindingPathProperty, value);
    }
    #endregion
        
    #region HeightBindingPath
    // another DP is defined here (all of them are strings)
        
    #region ForegroundBindingPath
    // and a third one, etc.
        

    // ===================== Change Handler: Creates the actual binding

    private static void BindingPathChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue is string source)                                                                // source property (could add DataContext by setting Value="source@datacontext" for example)
        {
            DependencyProperty target;                                                                  // which property is the target of the binding?
            if (e.Property == CompactPaneLengthBindingPathProperty) target = NavigationView.CompactPaneLengthProperty;
            else if (e.Property == HeightBindingPathProperty) target = FrameworkElement.HeightProperty;
            else if (e.Property == ForegroundBindingPathProperty) target = Control.ForegroundProperty;
            else throw new System.Exception(
quot;BindingHelper: Unknown target ({nameof(e.Property)}");    // don't know this property
        
            obj.ClearValue(target);                                                                     // clear previous bindings (and value)
            BindingOperations.SetBinding(obj, target,                                                   // set new binding (and value)
               new Binding { Path = new PropertyPath(source), Mode = BindingMode.OneWay });
        }
    }

Note that all of the DependencyProperties are of type string and that the target type can be any ancestor type of the control you are working with. For example, the HeightBindingPathProperty binding can be used with any FrameworkElement.

Use the helper in a Style just as you would any Setter, like this:

<Style x:Key="MyNavigationView" TargetType="controls:NavigationView" >
    <Setter Property="local:BindingHelper.CompactPaneLengthBindingPath" Value="CurrentCompactPaneLength" />
</Style>

I hope this helps.

卖梦商人 2025-02-20 18:20:11

今天早上,我对Winui的无意义限制将无尽的挫败感重定向到动机能量中,以制定通用解决方案。其他答案很棒,但这更可扩展:

public class SmartSetter
{
    /// <summary>
    /// The target <see cref="DependencyProperty"/> name, e.g.,
    /// <c>Background</c>. For an attached property, ensure the 
    /// <see cref="PropertyOwner"/> property is set, since this
    /// class doesn't have access to XAML namespaces. I would've
    /// loved to subclass Setter, but, sealed. Who'd have guessed.
    /// </summary>
    public string Property
    {
        get;
        set;
    }

    /// <summary>
    /// References the DependencyProperty owner when attached
    /// properties are targeted. Otherwise the owner is assumed
    /// to be the type of the FrameworkElement being styled.
    /// </summary>
    public Type PropertyOwner
    {
        get;
        set;
    }

    /// <summary>
    /// A BindingBase. In XAML, use normal binding notation, e.g.,
    /// <c>"{Binding RelativeSource={RelativeSource Mode=Self}, Path=SomeOtherProperty}"</c>
    /// </summary>
    public BindingBase Binding
    {
        get;
        set;
    }

    internal DependencyProperty ResolveProperty(Type ownerType)
    {
        if (_resolvedProperty != null)
            return _resolvedProperty;

        // The DP for WinUI types is exposed in reflection as a property, but for custom types it's a field. 

        var dpProp = type.GetProperty(
            $"{propertyName}Property", 
            BindingFlags.Static
                | BindingFlags.FlattenHierarchy
                | BindingFlags.Public);
        if (dpProp != null)
            return _resolvedProperty = dpProp.GetValue(null) as DependencyProperty;

        var dpField = type.GetField(
            $"{propertyName}Property",
            BindingFlags.Static
                | BindingFlags.FlattenHierarchy
                | BindingFlags.Public);
        if (dpField != null)
            return _resolvedProperty  = dpField.GetValue(null) as DependencyProperty;

        return null;
    }

    DependencyProperty _resolvedProperty;
}

/// <summary>
/// Provides a workaround for WinUI 3's omission of a means to
/// set bindings in Style Setters. In the main Style, assign an instance of 
/// <see cref="SmartStyle"/>, which includes a collection of <see cref="SmartSetter"/>s.
/// to the <see cref="SmartStyle.StyleProperty"/> attached property. 
/// </summary>
[ContentProperty(Name = nameof(Setters))]
public class SmartStyle
{
    Collection<SmartSetter> _setters;

    public Collection<SmartSetter> Setters => _setters ?? (_setters = new Collection<SmartSetter>());

    #region SmartStyle Style attached property
    public static readonly DependencyProperty StyleProperty = DependencyProperty.RegisterAttached(
        "Style",
        typeof(SmartStyle),
        typeof(SmartStyle),
        new PropertyMetadata(
            (SmartStyle)null,
            (obj, args) =>
            {
                if (!(obj is FrameworkElement fe) || !(args.NewValue is SmartStyle style))
                    return;
                foreach (var s in style.Setters)
                {
                    if (string.IsNullOrEmpty(s.Property) || s.Binding == null)
                        continue;
                    var dp = s.ResolveProperty(fe.GetType());
                    if (dp == null)
                        continue;
                    BindingOperations.SetBinding(obj, dp, s.Binding);
                }
            }));
    public static SmartStyle GetStyle(DependencyObject obj)
    {
        return (SmartStyle)obj.GetValue(StyleProperty);
    }
    public static void SetStyle(DependencyObject obj, SmartStyle style)
    {
        obj.SetValue(StyleProperty, style);
    }
    #endregion
}

用法:

<Style TargetType="Button"
       x:Key="SolidBackgroundButtonStyle">
    <Setter Property="local:SmartStyle.Style">
        <Setter.Value>
            <local:SmartStyle>
                <local:SmartSetter Property="BorderBrush"
                                   Binding="{Binding 
                                      RelativeSource={RelativeSource Mode=Self}, 
                                      Path=Background}" />
            </local:SmartStyle>
        </Setter.Value>
    </Setter>
</Style>

顺便说一句,它起作用的事实 - smartsetter.binding给出了bindingbase实例> bindingbase 实例,并且未实际评估绑定。在应用样式之前 - 告诉您Microsoft竭尽全力为setter做相反的事情,从而导致了这个问题,原因是我无法开始理解。

更新 - 我决定将此和另一个解决方法变成一个小项目。参见 https://github.com/peter0302/winui.redemption/empertion/

This morning I redirected my endless frustration with WinUI's senseless limitations into motivational energy to make a general purpose solution. Other answers are great but this is much more scalable:

public class SmartSetter
{
    /// <summary>
    /// The target <see cref="DependencyProperty"/> name, e.g.,
    /// <c>Background</c>. For an attached property, ensure the 
    /// <see cref="PropertyOwner"/> property is set, since this
    /// class doesn't have access to XAML namespaces. I would've
    /// loved to subclass Setter, but, sealed. Who'd have guessed.
    /// </summary>
    public string Property
    {
        get;
        set;
    }

    /// <summary>
    /// References the DependencyProperty owner when attached
    /// properties are targeted. Otherwise the owner is assumed
    /// to be the type of the FrameworkElement being styled.
    /// </summary>
    public Type PropertyOwner
    {
        get;
        set;
    }

    /// <summary>
    /// A BindingBase. In XAML, use normal binding notation, e.g.,
    /// <c>"{Binding RelativeSource={RelativeSource Mode=Self}, Path=SomeOtherProperty}"</c>
    /// </summary>
    public BindingBase Binding
    {
        get;
        set;
    }

    internal DependencyProperty ResolveProperty(Type ownerType)
    {
        if (_resolvedProperty != null)
            return _resolvedProperty;

        // The DP for WinUI types is exposed in reflection as a property, but for custom types it's a field. 

        var dpProp = type.GetProperty(
            
quot;{propertyName}Property", 
            BindingFlags.Static
                | BindingFlags.FlattenHierarchy
                | BindingFlags.Public);
        if (dpProp != null)
            return _resolvedProperty = dpProp.GetValue(null) as DependencyProperty;

        var dpField = type.GetField(
            
quot;{propertyName}Property",
            BindingFlags.Static
                | BindingFlags.FlattenHierarchy
                | BindingFlags.Public);
        if (dpField != null)
            return _resolvedProperty  = dpField.GetValue(null) as DependencyProperty;

        return null;
    }

    DependencyProperty _resolvedProperty;
}

/// <summary>
/// Provides a workaround for WinUI 3's omission of a means to
/// set bindings in Style Setters. In the main Style, assign an instance of 
/// <see cref="SmartStyle"/>, which includes a collection of <see cref="SmartSetter"/>s.
/// to the <see cref="SmartStyle.StyleProperty"/> attached property. 
/// </summary>
[ContentProperty(Name = nameof(Setters))]
public class SmartStyle
{
    Collection<SmartSetter> _setters;

    public Collection<SmartSetter> Setters => _setters ?? (_setters = new Collection<SmartSetter>());

    #region SmartStyle Style attached property
    public static readonly DependencyProperty StyleProperty = DependencyProperty.RegisterAttached(
        "Style",
        typeof(SmartStyle),
        typeof(SmartStyle),
        new PropertyMetadata(
            (SmartStyle)null,
            (obj, args) =>
            {
                if (!(obj is FrameworkElement fe) || !(args.NewValue is SmartStyle style))
                    return;
                foreach (var s in style.Setters)
                {
                    if (string.IsNullOrEmpty(s.Property) || s.Binding == null)
                        continue;
                    var dp = s.ResolveProperty(fe.GetType());
                    if (dp == null)
                        continue;
                    BindingOperations.SetBinding(obj, dp, s.Binding);
                }
            }));
    public static SmartStyle GetStyle(DependencyObject obj)
    {
        return (SmartStyle)obj.GetValue(StyleProperty);
    }
    public static void SetStyle(DependencyObject obj, SmartStyle style)
    {
        obj.SetValue(StyleProperty, style);
    }
    #endregion
}

Usage:

<Style TargetType="Button"
       x:Key="SolidBackgroundButtonStyle">
    <Setter Property="local:SmartStyle.Style">
        <Setter.Value>
            <local:SmartStyle>
                <local:SmartSetter Property="BorderBrush"
                                   Binding="{Binding 
                                      RelativeSource={RelativeSource Mode=Self}, 
                                      Path=Background}" />
            </local:SmartStyle>
        </Setter.Value>
    </Setter>
</Style>

Incidentally, the fact that this works - SmartSetter.Binding is given a BindingBase instance and the binding is not actually evaluated until the style is applied - tells you Microsoft went out of their way to do the opposite for Setter, and thereby cause this problem, for reasons I cannot begin to fathom.

Update - I decided to turn this and another workaround into a small project. See https://github.com/peter0302/WinUI.Redemption/

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文