简体   繁体   English

控制内容和属性更改

[英]Control Content and Property change

I have dialogbox with Content control with templates: 我有一个带有模板的内容控件的对话框:

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

and property change event at dialogbox context: 和属性更改事件在对话框上下文中:

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;

}

I want to change some properties at model, that determine which custom template will be displayed. 我想更改模型的某些属性,这些属性确定将显示哪个自定义模板。 To reload new template and invoke temlate selector should I create new model and 要重新加载新模板并调用temlate选择器,我应该创建新模型并

add property change event to this. 为此添加属性更改事件。 Is is ok, or is it another way to do this. 是可以的,还是执行此操作的另一种方法。

Update 更新资料

The below implementation doesn't work because it turns out that the template selector is only reinvoked if the actual value of ContentControl.Content changes. 下面的实现不起作用,因为事实证明,仅当ContentControl.Content的实际值更改时才重新调用模板选择器。 If you've still got the same instance of Model, raising PropertyChanged will have no effect. 如果仍然有相同的Model实例,则提高PropertyChanged将无效。 I even tried overriding ModelClass.Equals() and ModelClass.GetHashCode() . 我什至尝试覆盖ModelClass.Equals()ModelClass.GetHashCode() Neither was called. 没有人被召集。 Maybe the Binding is calling Object.ReferenceEquals() . 也许Binding正在调用Object.ReferenceEquals()

But I did find three ways to do this. 但是我确实找到了三种方法来做到这一点。 All have been tested, now that I've learned my lesson. 既然我已经学到了教训,所有的东西都已经过测试。

If you're going to this much trouble to get a template selector to work, best to look for some other approach where you're not fighting the framework. 如果要让模板选择器正常工作会遇到很多麻烦,那么最好在不使用框架的情况下寻找其他方法。

You could instead use style triggers to swap templates: 您可以改用样式触发器来交换模板:

<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>

...but the logic in your template selector may be quite a bit more complicated than that, in which case it may not be feasible. ...但是模板选择器中的逻辑可能比这复杂得多,在这种情况下,这可能不可行。

Here's another. 这是另一个 You don't need a template selector to select a template. 您不需要模板选择器即可选择模板。 A converter can return a DataTemplate too, and if you use a multi-binding converter, you can give it whatever it needs to look up a DataTemplate in the resources: 转换器也可以返回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# 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();
    }
}

One last possibility, and in my opinion the least, is to use the template selector, but make it work by actually replacing the value of Model every time one of its properties changes. 最后一种可能性(至少在我看来是这样)是使用模板选择器,但通过在每次其属性之一更改时实际替换Model的值来使其工作。 Rewrite ModelClass so it can easily be cloned: 重写ModelClass以便可以轻松克隆它:

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

...and keep _model_PropertyChanged from my original answer, but change the guts so instead of merely raising PropertyChanged , it replaces the actual value of Model (which will of course still raise PropertyChanged , as a side effect): ...并保留_model_PropertyChanged而不是我的原始答案,但是要改变胆量,而不是仅仅提高PropertyChanged ,而是替换Model的实际值(当然,它仍然会提高PropertyChanged ,这是一个副作用):

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

I've tested that and while it's alarmingly goofy, it does work. 我已经测试过了,虽然它令人震惊地愚蠢,但它确实可以工作。

Instead of cloning ModelClass , you could use a "reference" class for the parent's Model property: 代替克隆ModelClass ,您可以为父级的Model属性使用“引用”类:

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

But it's still wicked goofy. 但是它仍然是邪恶的愚蠢。 The viewmodel shouldn't "know" the view even exists, but here you are rewriting a chunk of it in a bizarre way just to work around a peculiarity in the implementation of a particular control. 视图模型不应该“知道”视图的存在,但是在这里,您要以一种怪异的方式重写其中的一部分,只是为了解决特定控件实现中的特殊性。 View workarounds belong in the view. 视图解决方法属于视图。


So when this.Model.Foo changes, you want to change the template? 因此,当this.Model.Foo更改时,您是否要更改模板? I would expect this to do the job: 我希望这能完成这项工作:

#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));
    }
}

This is defined in your viewmodel base class. 这是在您的viewmodel基类中定义的。 Maybe you've already got essentially the same method and it's called something else; 也许您已经有了本质上相同的方法,并且它被称为其他方法。 if so, use that one of course. 如果是这样,请当然使用该选项。

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

By the way, get rid of UpdateSourceTrigger=PropertyChanged . 顺便说一句, 摆脱UpdateSourceTrigger=PropertyChanged ContentControl will never create a new value for its Content property and pass that back to your viewmodel through the binding. ContentControl永远不会为其Content属性创建新值,并通过绑定将其传递回您的ViewModel。 Can't, won't, and you wouldn't want it to. 不能,不会,而且您也不想这么做。 So you don't need to tell it exactly when to perform a task it's not capable of performing. 因此,您无需确切告诉它何时执行它无法执行的任务。

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

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