控制内容和属性更改

时间:2017-02-20 14:05:37

标签: c# wpf

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

<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选择器,我应该创建新模型和

向此添加属性更改事件。可以,或者是另一种方法。

1 个答案:

答案 0 :(得分:1)

更新

以下实现不起作用,因为事实证明,如果ContentControl.Content的实际值发生变化,则仅重新调用模板选择器。如果您仍然拥有相同的模型实例,则提升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);
    }
}

我已经对此进行了测试,虽然它令人担忧,但确实有效。

您可以使用&#34;引用&#34;而不是克隆ModelClass。父级Model属性的类:

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

但它仍然是邪恶的傻瓜。视图模型不应该知道&#34;视图甚至存在,但在这里你以一种奇怪的方式重写它的一部分只是为了解决特定控件实现的特殊性。查看变通办法属于视图。

所以当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));

顺便说一句,get rid of UpdateSourceTrigger=PropertyChangedContentControl永远不会为其Content属性创建新值,并通过绑定将其传递回viewmodel。不能,不会,你也不会想要它。因此,您不需要准确地告诉它何时执行它无法执行的任务。