繁体   English   中英

控制内容和属性更改

[英]Control Content and Property change

我有一个带有模板的内容控件的对话框:

<ContentControl Content="{Binding Model,UpdateSourceTrigger=PropertyChanged}"  ContentTemplateSelector="{StaticResource TemplateSelector}"/>

和属性更改事件在对话框上下文中:

dialogContext.Model.PropertyChanged += (s, e) => Change(s,e, context);

private void Change(object s, PrropertyChangeEventArgs e, Context context)
{
  ...

  context.Mode = new Example()
  {
   ...
  }

  model.PropertyChanged += (sender, eventArgs) => 
          ModelChange(sender, eventArgs, context);
    context.Model = model;

}

我想更改模型的某些属性,这些属性确定将显示哪个自定义模板。 要重新加载新模板并调用temlate选择器,我应该创建新模型并

为此添加属性更改事件。 是可以的,还是执行此操作的另一种方法。

更新资料

下面的实现不起作用,因为事实证明,仅当ContentControl.Content的实际值更改时才重新调用模板选择器。 如果仍然有相同的Model实例,则提高PropertyChanged将无效。 我什至尝试覆盖ModelClass.Equals()ModelClass.GetHashCode() 没有人被召集。 也许Binding正在调用Object.ReferenceEquals()

但是我确实找到了三种方法来做到这一点。 既然我已经学到了教训,所有的东西都已经过测试。

如果要让模板选择器正常工作会遇到很多麻烦,那么最好在不使用框架的情况下寻找其他方法。

您可以改用样式触发器来交换模板:

<ContentControl
    Content="{Binding Model}"
    >
    <ContentControl.Style>
        <Style TargetType="ContentControl">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Model.Foo}" Value="foo">
                    <Setter 
                        Property="ContentTemplate" 
                        Value="{StaticResource Foo}" 
                        />
                </DataTrigger>
                <DataTrigger Binding="{Binding Model.Foo}" Value="bar">
                    <Setter 
                        Property="ContentTemplate" 
                        Value="{StaticResource Bar}" 
                        />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ContentControl.Style>
</ContentControl>

...但是模板选择器中的逻辑可能比这复杂得多,在这种情况下,这可能不可行。

这是另一个 您不需要模板选择器即可选择模板。 转换器也可以返回DataTemplate ,并且如果您使用多重绑定转换器,则可以根据需要在资源中查找DataTemplate的任何对象:

<ContentControl
    Content="{Binding Model}"
    >
    <ContentControl.ContentTemplate>
        <MultiBinding 
            Converter="{StaticResource ContentTemplateConverter}"
            >
            <!-- 
            We must bind to Model.Foo so the binding updates when that changes, 
            but we could also bind to Model as well if the converter wants to 
            look at other properties besides Foo. 
            -->
            <Binding Path="Model.Foo" />
            <!-- The ContentControl itself will be used for FindResource() -->
            <Binding RelativeSource="{RelativeSource Self}" />
        </MultiBinding>
    </ContentControl.ContentTemplate>
</ContentControl>

C#

public class ContentTemplateConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var ctl = values[1] as FrameworkElement;

        switch ($"{values[0]}")
        {
            case "foo":
                return ctl.FindResource("Foo") as DataTemplate;
            case "bar":
                return ctl.FindResource("Bar") as DataTemplate;
        }
        return null;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

最后一种可能性(至少在我看来是这样)是使用模板选择器,但通过在每次其属性之一更改时实际替换Model的值来使其工作。 重写ModelClass以便可以轻松克隆它:

public ModelClass() {}
public ModelClass(ModelClass cloneMe) {
    this.Foo = cloneMe.Foo;
    this.Bar = cloneMe.Bar;
}

...并保留_model_PropertyChanged而不是我的原始答案,但是要改变胆量,而不是仅仅提高PropertyChanged ,而是替换Model的实际值(当然,它仍然会提高PropertyChanged ,这是一个副作用):

private void _model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == nameof(ModelClass.Foo))
    {
        Model = new ModelClass(Model);
    }
}

我已经测试过了,虽然它令人震惊地愚蠢,但它确实可以工作。

代替克隆ModelClass ,您可以为父级的Model属性使用“引用”类:

public class ModelClassRef {
    public ModelClassRef(ModelClass mc) { ... }
    public ModelClassRef { get; private set; }
}

但是它仍然是邪恶的愚蠢。 视图模型不应该“知道”视图的存在,但是在这里,您要以一种怪异的方式重写其中的一部分,只是为了解决特定控件实现中的特殊性。 视图解决方法属于视图。


因此,当this.Model.Foo更改时,您是否要更改模板? 我希望这能完成这项工作:

#region Model Property
private ModelClass _model = null;
public ModelClass Model
{
    get { return _model; }
    set
    {
        if (value != _model)
        {
            if (_model != null)
            {
                _model.PropertyChanged -= _model_PropertyChanged;
            }
            _model = value;
            if (_model != null)
            {
                _model.PropertyChanged += _model_PropertyChanged;
            }
            OnPropertyChanged();
        }
    }
}

private void _model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    //  If Model.Foo changed, announce that Model changed. Any binding using 
    //  the Model property as its source will update, and that will cause 
    //  the template selector to be re-invoked. 

    if (e.PropertyName == nameof(ModelClass.Foo))
    {
        OnPropertyChanged(nameof(Model));
    }
}

这是在您的viewmodel基类中定义的。 也许您已经有了本质上相同的方法,并且它被称为其他方法。 如果是这样,请当然使用该选项。

protected void OnPropertyChanged([CallerMemberName] String propName = null)
    => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));

顺便说一句, 摆脱UpdateSourceTrigger=PropertyChanged ContentControl永远不会为其Content属性创建新值,并通过绑定将其传递回您的ViewModel。 不能,不会,而且您也不想这么做。 因此,您无需确切告诉它何时执行它无法执行的任务。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM