gpt4 book ai didi

c# - 带有标记扩展名的字符串格式

转载 作者:可可西里 更新时间:2023-11-01 08:26:22 26 4
gpt4 key购买 nike

我正在尝试将 string.Format 用作 WPF 中的一个方便的函数,以便可以在纯 XAML 中组合各种文本部分,而无需代码隐藏中的样板。主要问题是支持函数的参数来自其他嵌套标记扩展(例如 Binding )的情况。

实际上,有一个功能非常接近我需要的功能: MultiBinding 。不幸的是,它只能接受绑定(bind),而不能接受其他动态类型的内容,例如 DynamicResource s。

如果我所有的数据源都是绑定(bind),我可以使用这样的标记:

<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource StringFormatConverter}">
<Binding Path="FormatString"/>
<Binding Path="Arg0"/>
<Binding Path="Arg1"/>
<!-- ... -->
</MultiBinding>
</TextBlock.Text>
</TextBlock>

明显实现 StringFormatConveter

我试图实现一个自定义标记扩展,以便语法是这样的:
<TextBlock>
<TextBlock.Text>
<l:StringFormat Format="{Binding FormatString}">
<DynamicResource ResourceKey="ARG0ID"/>
<Binding Path="Arg1"/>
<StaticResource ResourceKey="ARG2ID"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>

或者也许只是
<TextBlock Text="{l:StringFormat {Binding FormatString},
arg0={DynamicResource ARG0ID},
arg1={Binding Arg2},
arg2='literal string', ...}"/>

但是对于参数是另一个标记扩展的情况,我坚持 ProvideValue(IServiceProvider serviceProvider) 的实现。

互联网上的大多数示例都非常简单:它们要么根本不使用 serviceProvider,要么查询 IProvideValueTarget ,它(主要)说明了标记扩展的目标是什么依赖属性。在任何情况下,代码都知道应该在 ProvideValue 调用时提供的值。但是, ProvideValue 只会被调用一次( except for templates ,这是一个单独的故事),因此如果实际值不是恒定的(例如 Binding 等),则应使用另一种策略。

我查了一下 Reflector 中 Binding 的实现,它的 ProvideValue 方法实际上返回的不是真正的目标对象,而是 System.Windows.Data.BindingExpression 类的一个实例,它似乎做了所有的实际工作。关于 DynamicResource 也是如此:它只返回 System.Windows.ResourceReferenceExpression 的一个实例,它关心订阅(内部) InheritanceContextChanged 并在适当的时候使该值无效。但是,通过查看代码,我无法理解以下内容:
  • 类型 BindingExpression/ResourceReferenceExpression 的对象未被“按原样”处理,而是被要求提供基础值,这是怎么回事?
  • MultiBindingExpression 如何知道底层绑定(bind)的值发生了变化,因此它也必须使其值失效?

  • 我实际上发现了一个标记扩展库实现,它声称支持连接字符串(它完美地映射到我的用例)( projectcodeconcatenation implementation 依赖于 other code ),但它似乎只支持嵌套的库类型扩展(即,您不能在内部嵌套 Vanilla Binding)。

    有没有办法实现问题顶部提供的语法?它是受支持的场景,还是只能从 WPF 框架内部执行此操作(因为 System.Windows.Expression 具有内部构造函数)?

    实际上,我使用自定义的不可见帮助器 UI 元素实现了所需的语义:
    <l:FormatHelper x:Name="h1" Format="{DynamicResource FORMAT_ID'">
    <l:FormatArgument Value="{Binding Data1}"/>
    <l:FormatArgument Value="{StaticResource Data2}"/>
    </l:FormatHelper>
    <TextBlock Text="{Binding Value, ElementName=h1}"/>

    (其中 FormatHelper 跟踪其子项及其依赖项属性更新,并将最新结果存储到 Value 中),但这种语法似乎很难看,我想摆脱可视化树中的辅助项。

    最终目标是促进翻译:像“15 秒直到爆炸”这样的 UI 字符串自然地表示为可本地化的格式“{0}直到爆炸”(进入 ResourceDictionary 并在语言改变时将被替换)和 Binding 到表示时间的 VM 依赖属性。

    更新报告 :我尝试使用我在互联网上可以找到的所有信息自己实现标记扩展。完整的实现在这里( [1][2][3] ),这里是核心部分:
    var result = new MultiBinding()
    {
    Converter = new StringFormatConverter(),
    Mode = BindingMode.OneWay
    };

    foreach (var v in values)
    {
    if (v is MarkupExtension)
    {
    var b = v as Binding;
    if (b != null)
    {
    result.Bindings.Add(b);
    continue;
    }

    var bb = v as BindingBase;
    if (bb != null)
    {
    targetObjFE.SetBinding(AddBindingTo(targetObjFE, result), bb);
    continue;
    }
    }

    if (v is System.Windows.Expression)
    {
    DynamicResourceExtension mex = null;
    // didn't find other way to check for dynamic resource
    try
    {
    // rrc is a new ResourceReferenceExpressionConverter();
    mex = (MarkupExtension)rrc.ConvertTo(v, typeof(MarkupExtension))
    as DynamicResourceExtension;
    }
    catch (Exception)
    {
    }
    if (mex != null)
    {
    targetObjFE.SetResourceReference(
    AddBindingTo(targetObjFE, result),
    mex.ResourceKey);
    continue;
    }
    }

    // fallback
    result.Bindings.Add(
    new Binding() { Mode = BindingMode.OneWay, Source = v });
    }

    return result.ProvideValue(serviceProvider);

    这似乎适用于嵌套绑定(bind)和动态资源,但在尝试将其嵌套在自身中时失败了,因为在这种情况下,从 targetObj 获得的 IProvideValueTargetnull 。我试图通过将嵌套绑定(bind)合并到外部绑定(bind)( [1a][2a] )来解决这个问题(将多绑定(bind)溢出添加到外部绑定(bind)中),这可能适用于嵌套的多绑定(bind)和格式扩展,但仍然无法使用嵌套的动态资源。

    有趣的是,当嵌套不同类型的标记扩展时,我在外部扩展中得到 Binding s 和 MultiBinding s,但是 ResourceReferenceExpression 而不是 DynamicResourceExtension 。我想知道为什么它不一致(以及如何从 Binding 重建 BindingExpression )。

    更新报告 :不幸的是,答案中给出的想法并没有带来问题的解决方案。也许这证明了标记扩展虽然是非常强大和通用的工具,但需要 WPF 团队更多的关注。

    无论如何,我感谢任何参与讨论的人。提出的部分解决方案足够复杂,值得更多的赞成。

    更新报告 :标记扩展似乎没有好的解决方案,或者至少创建标记扩展所需的 WPF 知识水平太深而不实用。

    然而,@adabyron 有一个改进的想法,这有助于隐藏宿主项目中的辅助元素(然而,这样做的代价是对宿主进行子类化)。我会尝试看看是否有可能摆脱子类化(使用劫持主机的 LogicalChildren 并在我的脑海中添加辅助元素的行为,灵感来自同一答案的旧版本)。

    最佳答案

    看看以下是否适合你。我拿了test case您在评论中提供并稍微扩展以更好地说明该机制。我想关键是通过使用 DependencyProperties 来保持灵活性。在嵌套容器中。

    enter image description here enter image description here

    编辑 :我已经用 TextBlock 的子类替换了混合行为。这为 DataContext 和 DynamicResources 添加了更简单的链接。

    在旁注中,您的项目使用的方式 DynamicResources引入条件不是我推荐的。而是尝试使用 ViewModel 来建立条件,和/或使用触发器。

    Xml:

    <UserControl x:Class="WpfApplication1.Controls.ExpiryView" xmlns:system="clr-namespace:System;assembly=mscorlib" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:props="clr-namespace:WpfApplication1.Properties" xmlns:models="clr-namespace:WpfApplication1.Models"
    xmlns:h="clr-namespace:WpfApplication1.Helpers" xmlns:c="clr-namespace:WpfApplication1.CustomControls"
    Background="#FCF197" FontFamily="Segoe UI"
    TextOptions.TextFormattingMode="Display"> <!-- please notice the effect of this on font fuzzyness -->

    <UserControl.DataContext>
    <models:ExpiryViewModel />
    </UserControl.DataContext>
    <UserControl.Resources>
    <system:String x:Key="ShortOrLongDateFormat">{0:d}</system:String>
    </UserControl.Resources>
    <Grid>
    <StackPanel>
    <c:TextBlockComplex VerticalAlignment="Center" HorizontalAlignment="Center">
    <c:TextBlockComplex.Content>
    <h:StringFormatContainer StringFormat="{x:Static props:Resources.ExpiryDate}">
    <h:StringFormatContainer.Values>
    <h:StringFormatContainer Value="{Binding ExpiryDate}" StringFormat="{DynamicResource ShortOrLongDateFormat}" />
    <h:StringFormatContainer Value="{Binding SecondsToExpiry}" />
    </h:StringFormatContainer.Values>
    </h:StringFormatContainer>
    </c:TextBlockComplex.Content>
    </c:TextBlockComplex>
    </StackPanel>
    </Grid>
    </UserControl>

    文本块复合体:
    using System;
    using System.Collections;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Windows;
    using System.Windows.Controls;
    using WpfApplication1.Helpers;

    namespace WpfApplication1.CustomControls
    {
    public class TextBlockComplex : TextBlock
    {
    // Content
    public StringFormatContainer Content { get { return (StringFormatContainer)GetValue(ContentProperty); } set { SetValue(ContentProperty, value); } }
    public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(StringFormatContainer), typeof(TextBlockComplex), new PropertyMetadata(null));

    private static readonly DependencyPropertyDescriptor _dpdValue = DependencyPropertyDescriptor.FromProperty(StringFormatContainer.ValueProperty, typeof(StringFormatContainer));
    private static readonly DependencyPropertyDescriptor _dpdValues = DependencyPropertyDescriptor.FromProperty(StringFormatContainer.ValuesProperty, typeof(StringFormatContainer));
    private static readonly DependencyPropertyDescriptor _dpdStringFormat = DependencyPropertyDescriptor.FromProperty(StringFormatContainer.StringFormatProperty, typeof(StringFormatContainer));
    private static readonly DependencyPropertyDescriptor _dpdContent = DependencyPropertyDescriptor.FromProperty(TextBlockComplex.ContentProperty, typeof(StringFormatContainer));

    private EventHandler _valueChangedHandler;
    private NotifyCollectionChangedEventHandler _valuesChangedHandler;

    protected override IEnumerator LogicalChildren { get { yield return Content; } }

    static TextBlockComplex()
    {
    // take default style from TextBlock
    DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBlockComplex), new FrameworkPropertyMetadata(typeof(TextBlock)));
    }

    public TextBlockComplex()
    {
    _valueChangedHandler = delegate { AddListeners(this.Content); UpdateText(); };
    _valuesChangedHandler = delegate { AddListeners(this.Content); UpdateText(); };

    this.Loaded += TextBlockComplex_Loaded;
    }

    void TextBlockComplex_Loaded(object sender, RoutedEventArgs e)
    {
    OnContentChanged(this, EventArgs.Empty); // initial call

    _dpdContent.AddValueChanged(this, _valueChangedHandler);
    this.Unloaded += delegate { _dpdContent.RemoveValueChanged(this, _valueChangedHandler); };
    }

    /// <summary>
    /// Reacts to a new topmost StringFormatContainer
    /// </summary>
    private void OnContentChanged(object sender, EventArgs e)
    {
    this.AddLogicalChild(this.Content); // inherits DataContext
    _valueChangedHandler(this, EventArgs.Empty);
    }

    /// <summary>
    /// Updates Text to the Content values
    /// </summary>
    private void UpdateText()
    {
    this.Text = Content.GetValue() as string;
    }

    /// <summary>
    /// Attaches listeners for changes in the Content tree
    /// </summary>
    private void AddListeners(StringFormatContainer cont)
    {
    // in case they have been added before
    RemoveListeners(cont);

    // listen for changes to values collection
    cont.CollectionChanged += _valuesChangedHandler;

    // listen for changes in the bindings of the StringFormatContainer
    _dpdValue.AddValueChanged(cont, _valueChangedHandler);
    _dpdValues.AddValueChanged(cont, _valueChangedHandler);
    _dpdStringFormat.AddValueChanged(cont, _valueChangedHandler);

    // prevent memory leaks
    cont.Unloaded += delegate { RemoveListeners(cont); };

    foreach (var c in cont.Values) AddListeners(c); // recursive
    }

    /// <summary>
    /// Detaches listeners
    /// </summary>
    private void RemoveListeners(StringFormatContainer cont)
    {
    cont.CollectionChanged -= _valuesChangedHandler;

    _dpdValue.RemoveValueChanged(cont, _valueChangedHandler);
    _dpdValues.RemoveValueChanged(cont, _valueChangedHandler);
    _dpdStringFormat.RemoveValueChanged(cont, _valueChangedHandler);
    }
    }
    }

    字符串格式容器:
    using System.Linq;
    using System.Collections;
    using System.Collections.ObjectModel;
    using System.Windows;

    namespace WpfApplication1.Helpers
    {
    public class StringFormatContainer : FrameworkElement
    {
    // Values
    private static readonly DependencyPropertyKey ValuesPropertyKey = DependencyProperty.RegisterReadOnly("Values", typeof(ObservableCollection<StringFormatContainer>), typeof(StringFormatContainer), new FrameworkPropertyMetadata(new ObservableCollection<StringFormatContainer>()));
    public static readonly DependencyProperty ValuesProperty = ValuesPropertyKey.DependencyProperty;
    public ObservableCollection<StringFormatContainer> Values { get { return (ObservableCollection<StringFormatContainer>)GetValue(ValuesProperty); } }

    // StringFormat
    public static readonly DependencyProperty StringFormatProperty = DependencyProperty.Register("StringFormat", typeof(string), typeof(StringFormatContainer), new PropertyMetadata(default(string)));
    public string StringFormat { get { return (string)GetValue(StringFormatProperty); } set { SetValue(StringFormatProperty, value); } }

    // Value
    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(StringFormatContainer), new PropertyMetadata(default(object)));
    public object Value { get { return (object)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } }

    public StringFormatContainer()
    : base()
    {
    SetValue(ValuesPropertyKey, new ObservableCollection<StringFormatContainer>());
    this.Values.CollectionChanged += OnValuesChanged;
    }

    /// <summary>
    /// The implementation of LogicalChildren allows for DataContext propagation.
    /// This way, the DataContext needs only be set on the outermost instance of StringFormatContainer.
    /// </summary>
    void OnValuesChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
    if (e.NewItems != null)
    {
    foreach (var value in e.NewItems)
    AddLogicalChild(value);
    }
    if (e.OldItems != null)
    {
    foreach (var value in e.OldItems)
    RemoveLogicalChild(value);
    }
    }

    /// <summary>
    /// Recursive function to piece together the value from the StringFormatContainer hierarchy
    /// </summary>
    public object GetValue()
    {
    object value = null;
    if (this.StringFormat != null)
    {
    // convention: if StringFormat is set, Values take precedence over Value
    if (this.Values.Any())
    value = string.Format(this.StringFormat, this.Values.Select(v => (object)v.GetValue()).ToArray());
    else if (Value != null)
    value = string.Format(this.StringFormat, Value);
    }
    else
    {
    // convention: if StringFormat is not set, Value takes precedence over Values
    if (Value != null)
    value = Value;
    else if (this.Values.Any())
    value = string.Join(string.Empty, this.Values);
    }
    return value;
    }

    protected override IEnumerator LogicalChildren
    {
    get
    {
    if (Values == null) yield break;
    foreach (var v in Values) yield return v;
    }
    }
    }
    }

    到期 View 模型:
    using System;
    using System.ComponentModel;

    namespace WpfApplication1.Models
    {
    public class ExpiryViewModel : INotifyPropertyChanged
    {
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
    if (this.PropertyChanged != null)
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    private DateTime _expiryDate;
    public DateTime ExpiryDate { get { return _expiryDate; } set { _expiryDate = value; OnPropertyChanged("ExpiryDate"); } }

    public int SecondsToExpiry { get { return (int)ExpiryDate.Subtract(DateTime.Now).TotalSeconds; } }

    public ExpiryViewModel()
    {
    this.ExpiryDate = DateTime.Today.AddDays(2.67);

    var timer = new System.Timers.Timer(1000);
    timer.Elapsed += (s, e) => OnPropertyChanged("SecondsToExpiry");
    timer.Start();
    }
    }
    }

    关于c# - 带有标记扩展名的字符串格式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25415400/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com