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