29

これが私が持っているXAMLです:

<ItemsControl ItemsSource="{Binding Path=Groups}" ItemTemplateSelector="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=ListTemplateSelector}"/>

これが私のListTemplateSelectorクラスです:

public class ListTemplateSelector : DataTemplateSelector {
public DataTemplate GroupTemplate { get; set; }
public DataTemplate ItemTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
    GroupList<Person> list = item as GroupList<Person>;
    if (list != null && !list.IsLeaf)
        return GroupTemplate;
    return ItemTemplate;
}
}

GroupTemplateデータテンプレートはそれ自体の内部でListTemplateSelectorを参照するため、これが私が設定したように設定した理由です。これは、私がまとめることができる唯一の再帰的ハックです。しかし、それは私が抱えている問題ではありません。

私の問題は、IsLeafプロパティが変更されたときにItemTemplateからGroupTemplateに変更したいということです。これは、プロパティを初めて読み取るため、初めて美しく機能します。ただし、このプロパティが変更されると、テンプレートセレクターは再適用されません。これで、トリガーを使用して値にバインドし、アイテムテンプレートを適切に設定できますが、状態が異なる可能性があるため、アイテムごとに異なるテンプレートを設定できる必要があります。

たとえば、次のようなグループのリストがあるとします。

グループ1:IsLeaf = false、つまりtemplate = GroupTemplate

グループ2:IsLeaf = true、つまりtemplate = ItemTemplate

グループ3:IsLeaf = false、つまりtemplate = GroupTemplate

また、グループ1のIsLeafプロパティがtrueに変更されると、テンプレートは自動的にItemTemplateに変更される必要があります。

編集:

これが私の一時的な解決策です。それを行うためのより良い方法はありますか?

<ItemsControl ItemsSource="{Binding Path=Groups}">
<ItemsControl.ItemTemplate>
    <DataTemplate>
        <ContentControl Content="{Binding}">
            <ContentControl.Style>
                <Style TargetType="{x:Type ContentControl}">
                    <Setter Property="ContentTemplate" Value="{DynamicResource ItemTemplate}"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Path=IsLeaf}" Value="False">
                            <Setter Property="ContentTemplate" Value="{DynamicResource GroupTemplate}"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </ContentControl.Style>
        </ContentControl>
    </DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
4

5 に答える 5

25

EDITに関しては、 ?Triggerを使用する代わりにDataTemplateで十分ではないでしょうか。Styleあれは:

<ItemsControl ItemsSource="{Binding Path=Groups}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <ContentControl x:Name="cc" Content="{Binding}" ContentTemplate="{DynamicResource ItemTemplate}"/>

            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding Path=IsLeaf}" Value="False">
                    <Setter TargetName="cc" Property="ContentTemplate" Value="{DynamicResource GroupTemplate}"/>
                </DataTrigger>
            </DataTemplate.Triggers>                            
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
于 2010-08-23T03:07:33.340 に答える
22

この回避策は私には簡単に思えます。TemplateSelector内から、気になるプロパティをリッスンしてから、テンプレートセレクターを再適用して強制的に更新します。

public class DataSourceTemplateSelector : DataTemplateSelector
{
    public DataTemplate IA { get; set; }
    public DataTemplate Dispatcher { get; set; }
    public DataTemplate Sql { get; set; }

    public override DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
    {
        var ds = item as DataLocationViewModel;
        if (ds == null)
        {
            return base.SelectTemplate(item, container);
        }
        PropertyChangedEventHandler lambda = null;
        lambda = (o, args) =>
            {
                if (args.PropertyName == "SelectedDataSourceType")
                {
                    ds.PropertyChanged -= lambda;
                    var cp = (ContentPresenter)container;
                    cp.ContentTemplateSelector = null;
                    cp.ContentTemplateSelector = this;                        
                }
            };
        ds.PropertyChanged += lambda;

        switch (ds.SelectedDataSourceType.Value)
        {
            case DataSourceType.Dispatcher:
                return Dispatcher;
            case DataSourceType.IA:
                return IA;
            case DataSourceType.Sql:
                return Sql;
            default:
                throw new NotImplementedException(ds.SelectedDataSourceType.Value.ToString());
        }
    }
}
于 2013-08-01T17:06:17.037 に答える
4

元のソリューションに戻り、「テンプレートセレクターが再適用されない」という問題:次のようにビューを更新できます

CollectionViewSource.GetDefaultView(YourItemsControl.ItemsSource).Refresh();

簡潔にするために、ItemsControlは、XAMLに追加された名前( "YourItemsControl")によって参照されます。

<ItemsControl x:Name="YourItemsControl" ItemsSource="{Binding Path=Groups}" 
ItemTemplateSelector="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=ListTemplateSelector}"/>

唯一の問題は、この更新手順のためにプロジェクト内の適切な場所をどのように選択するかということかもしれません。ビューコードビハインドに入る可能性があります。または、IsLeafがDPの場合、適切な場所は依存関係プロパティが変更されたコールバックになります。

于 2014-10-10T06:10:54.123 に答える
1

私はバインディングプロキシでそれを行います。

これは通常のバインディングプロキシのように機能します(ただし、2つのプロップを使用-DataInからDataOutにデータをコピーします)が、トリガー値が変更されるたびにDataOutをNULLに設定し、DataIn値に戻します。

public class BindingProxyForTemplateSelector : Freezable
{
    #region Overrides of Freezable

    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxyForTemplateSelector();
    }

    #endregion

    public object DataIn
    {
        get { return (object)GetValue(DataInProperty); }
        set { SetValue(DataInProperty, value); }
    }

    public object DataOut
    {
        get { return (object) GetValue(DataOutProperty); }
        set { SetValue(DataOutProperty, value); }
    }

    public object Trigger
    {
        get { return (object) GetValue(TriggerProperty); }
        set { SetValue(TriggerProperty, value); }
    }


    public static readonly DependencyProperty TriggerProperty = DependencyProperty.Register(nameof(Trigger), typeof(object), typeof(BindingProxyForTemplateSelector), new PropertyMetadata(default(object), OnTriggerValueChanged));

    public static readonly DependencyProperty DataInProperty = DependencyProperty.Register(nameof(DataIn), typeof(object), typeof(BindingProxyForTemplateSelector), new UIPropertyMetadata(null, OnDataChanged));

    public static readonly DependencyProperty DataOutProperty = DependencyProperty.Register(nameof(DataOut), typeof(object), typeof(BindingProxyForTemplateSelector), new PropertyMetadata(default(object)));



    private static void OnTriggerValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // this does the whole trick

        var sender = d as BindingProxyForTemplateSelector;
        if (sender == null)
            return;

        sender.DataOut = null; // set to null and then back triggers the TemplateSelector to search for a new template
        sender.DataOut = sender.DataIn;
    }



    private static void OnDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var sender = d as BindingProxyForTemplateSelector;
        if (sender == null)
            return;

        sender.DataOut = e.NewValue;
    }

}

次のように使用します。

<Grid>
    <Grid.Resources>
        <local:BindingProxyForTemplateSelector DataIn="{Binding}" Trigger="{Binding Item.SomeBool}" x:Key="BindingProxy"/>
    </Grid.Resources>
    <ContentControl Content="{Binding Source={StaticResource BindingProxy}, Path=DataOut.Item}" ContentTemplateSelector="{StaticResource TemplateSelector}"/>
</Grid>

したがって、DataContextに直接バインドするのではなく、元のDataContextをミラーリングするBindingProxyのDataOutにバインドしますが、わずかな違いがあります。トリガーが変更されると(この例では、「Item」内のブール値)、TemplateSelectorは次のようになります。再トリガーされました。

このためにTemplateSelectorを変更する必要はありません。

Trigger2を追加するだけで、さらにTriggerを追加することもできます。

于 2017-08-03T14:12:49.470 に答える
0

私は、セレクターに変更をチェックさせる方法を投稿するソリューションに本当に満足していませんでした。

public class DynamicSelectorContentControl : ContentControl
{
    // Using a DependencyProperty as the backing store for ListenToProperties.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ListenToPropertiesProperty =
        DependencyProperty.Register("ListenToProperties", typeof(string),
            typeof(DynamicSelectorContentControl),
            new FrameworkPropertyMetadata(string.Empty));

    public DynamicSelectorContentControl()
    {
        this.DataContextChanged += DynamicSelectorContentControl_DataContextChanged;
    }

    public string ListenToProperties
    {
        get { return (string)GetValue(ListenToPropertiesProperty); }
        set { SetValue(ListenToPropertiesProperty, value); }
    }
    private void CheckForProperty(object sender, PropertyChangedEventArgs e)
    {
        if (ListenToProperties.Contains(e.PropertyName))
        {
            ClearSelector();
        }
    }

    private void ClearSelector()
    {
        var oldSelector = this.ContentTemplateSelector;
        if (oldSelector != null)
        {
            this.ContentTemplateSelector = null;
            this.ContentTemplateSelector = oldSelector;
        }
    }

    private void DynamicSelectorContentControl_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
    {
        var listOfProperties = ListenToProperties.Split(',').Select(s => s.Trim());

        var oldObservable = e.OldValue as INotifyPropertyChanged;

        if (oldObservable != null && listOfProperties.Any())
        {
            PropertyChangedEventManager.RemoveHandler(oldObservable, CheckForProperty, string.Empty);
        }

        var newObservable = e.NewValue as INotifyPropertyChanged;

        if (newObservable != null && listOfProperties.Any())
        {
            PropertyChangedEventManager.AddHandler(newObservable, CheckForProperty, string.Empty);
        }

        if (e.OldValue != null)
        {
            ClearSelector();
        }
    }
}

XAMLでの使用法:

                                <controls:DynamicSelectorContentControl DockPanel.Dock="Top"
                                            ContentTemplateSelector="{StaticResource AgeGenderSelector}"
                                            ListenToProperties="Gender, Age"                        
                                            Content="{Binding .}"/>

これを変更して依存関係をリストにすることもできますが、私の場合は文字列の方が適していました。それはうまく機能し、メモリリークはありません。さらに、メインのxamlをガベージしない追加ファイルにDataTemplatesを含めることができます。

乾杯、マルコ

于 2019-02-12T12:44:55.043 に答える