在xaml中使用数据模板时如何对不同的项目设置不同的样式?

发布于 2024-08-22 11:07:41 字数 1158 浏览 3 评论 0原文

我希望从数据模板生成的元素看起来有所不同,具体取决于模型中的某些属性。例如,我想以各种颜色显示文本或为每个生成的元素呈现不同的图像或路径图。我一般知道如何做到这一点,但我正在寻找一种解决方案,允许设计师在不接触代码的情况下编辑 Expression Blend 中的视觉细节。例如,最简单的解决方案:

   <DataTemplate>
        <StackPanel Orientation="Horizontal"> 
            <Image Source="{Binding MyImageSource}"/>
            <TextBlock Width="200"  Text="{Binding MyText}" Forecolor="{Binding MyColor}"></TextBox> 
        </StackPanel> 
    </DataTemplate> 

其中“MyImageSource”和“MyColor”是项目模型(ImageSource 和 Brush 类型)的属性,不能满足我的需求,因为我不想分配这些值。我希望设计师能做到这一点。我的模型将具有枚举或字符串(或其他类型)类型的“ItemType”或“ItemStyle”属性,而不是“MyImageSource”和“MyColor”属性。我不是在寻找“宗教”MVVM 严格的解决方案。我唯一的要求是避免设计师等待我按照他的指示更正代码,例如“将列表 Y 中类型 X 的项目颜色更改为 #FFAACC”,因为这似乎在某种程度上违反了 SoC 规则。

编辑(基于答案):

我找到了与bendewey描述的类似的解决方案此处 - 它需要使用 ItemsSource 属性为控件派生自定义控件。为每种元素类型使用不同的数据模板的想法很巧妙,但在我看来,它涵盖了我们想要为每个项目生成完全不同的视觉元素的情况。当元素仅在某些颜色和图像上有所不同(并且除此之外还包含许多常见元素)时,为每个元素类型创建单独的数据模板将导致不必要的代码(xaml)重复。在这种情况下,弗拉德的解决方案更适合。 除了这两种技术之外,还有其他技术吗?

I would like my elements generated from datatemplate to look differently depending on some properties in the model. For example I want to display the text in various colors or present different images or path drawing for each generatred element. I know how to do it in general, but I'm looking for a solution that would allow editing the visual details in Expression Blend by designer without touching the code. For example the simplest solution:

   <DataTemplate>
        <StackPanel Orientation="Horizontal"> 
            <Image Source="{Binding MyImageSource}"/>
            <TextBlock Width="200"  Text="{Binding MyText}" Forecolor="{Binding MyColor}"></TextBox> 
        </StackPanel> 
    </DataTemplate> 

Where 'MyImageSource' and 'MyColor' are properties of the item model (of type ImageSource and Brush) does not satisfy my needs because I don't want to assign these values. I want the designer to do that. Instead of 'MyImageSource' and 'MyColor' properties my model would have a property like 'ItemType' or 'ItemStyle' of type enum or string (or some other type). I'm not looking for "religious" MVVM strict solution. My only requirement is to avoid the need for the designer to wait for me to correct the code following his instructions like "change the item color of type X in list Y to #FFAACC" because it seems like breaking the SoC rule in a way.

EDIT (based on the answers):

I've found similar solution to the one described by bendewey here - it requires to derive custom control for the control using ItemsSource attribute. The idea of using different datatemplates for each element type is neat, but in my opinion it covers the situation when we want to generate completely different visual elements for each item. When the elements differ only in some colors and image (and contain lots of common elements apart from that), then creating separate datatemplate for each element type would cause unnecessary code (xaml) repetition. In this situation Vlad's solution suits better.
Is there any other technique apart from theese two?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

笑忘罢 2024-08-29 11:07:41

您可以从 WPF playbook 中窃取 ItemTemplateSelector 概念。这就是它的样子:

App.xaml

<DataTemplate x:Key="MaleTemplate">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="M - " />
        <TextBlock Text="{Binding Name}" />
    </StackPanel>
</DataTemplate>

<DataTemplate x:Key="FemaleTemplate">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="F - " />
        <TextBlock Text="{Binding Name}" />
    </StackPanel>
</DataTemplate>

MainPage.xaml

<UserControl.Resources>
    <local:PersonTemplateSelector x:Key="PersonTemplateSelector" />
</UserControl.Resources>

<Grid x:Name="LayoutRoot">
    <local:CustomItemsControl ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource PersonTemplateSelector}" />
</Grid>

MainPage.xaml.cs

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();
        this.DataContext = Person.GetSampleData();
    }
}

PersonTemplateSelector.cs

public class PersonTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var person = item as Person;
        if (person != null)
        {
            switch (person.Gender)
            {
                case Gender.Male:
                    return Application.Current.Resources["MaleTemplate"] as DataTemplate;
                case Gender.Female:
                    return Application.Current.Resources["FemaleTemplate"] as DataTemplate;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
        return null;
    }
}

如果这是从那时起并且您有兴趣使用的东西,您将需要使用此 CustomItemsControl 或某些变体:

CustomItemsControl.cs

public class CustomItemsControl : ItemsControl
{
    public DataTemplateSelector ItemTemplateSelector
    {
        get { return (DataTemplateSelector)GetValue(ItemTemplateSelectorProperty); }
        set { SetValue(ItemTemplateSelectorProperty, value); }
    }

    public static readonly DependencyProperty ItemTemplateSelectorProperty =
        DependencyProperty.Register("ItemTemplateSelector", typeof(DataTemplateSelector), typeof(CustomItemsControl), new PropertyMetadata(null));

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        ContentPresenter presenter = element as ContentPresenter;

        if (presenter != null)
        {
            DataTemplate itemTemplate = null;
            if (base.ItemTemplate != null)
            {
                itemTemplate = base.ItemTemplate;
            }
            else if (this.ItemTemplateSelector != null)
            {
                itemTemplate = this.ItemTemplateSelector.SelectTemplate(item, element);
            }

            presenter.Content = item;
            presenter.ContentTemplate = itemTemplate;
        }
    }
}

public class DataTemplateSelector
{
    public virtual DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        return null;
    }
}

You can steal the ItemTemplateSelector concept from the WPF playbook. This is what that would look like:

App.xaml

<DataTemplate x:Key="MaleTemplate">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="M - " />
        <TextBlock Text="{Binding Name}" />
    </StackPanel>
</DataTemplate>

<DataTemplate x:Key="FemaleTemplate">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="F - " />
        <TextBlock Text="{Binding Name}" />
    </StackPanel>
</DataTemplate>

MainPage.xaml

<UserControl.Resources>
    <local:PersonTemplateSelector x:Key="PersonTemplateSelector" />
</UserControl.Resources>

<Grid x:Name="LayoutRoot">
    <local:CustomItemsControl ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource PersonTemplateSelector}" />
</Grid>

MainPage.xaml.cs

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();
        this.DataContext = Person.GetSampleData();
    }
}

PersonTemplateSelector.cs

public class PersonTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var person = item as Person;
        if (person != null)
        {
            switch (person.Gender)
            {
                case Gender.Male:
                    return Application.Current.Resources["MaleTemplate"] as DataTemplate;
                case Gender.Female:
                    return Application.Current.Resources["FemaleTemplate"] as DataTemplate;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
        return null;
    }
}

If this is something that makes since and you are interested in using you will need to use this CustomItemsControl, or some variation:

CustomItemsControl.cs

public class CustomItemsControl : ItemsControl
{
    public DataTemplateSelector ItemTemplateSelector
    {
        get { return (DataTemplateSelector)GetValue(ItemTemplateSelectorProperty); }
        set { SetValue(ItemTemplateSelectorProperty, value); }
    }

    public static readonly DependencyProperty ItemTemplateSelectorProperty =
        DependencyProperty.Register("ItemTemplateSelector", typeof(DataTemplateSelector), typeof(CustomItemsControl), new PropertyMetadata(null));

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        ContentPresenter presenter = element as ContentPresenter;

        if (presenter != null)
        {
            DataTemplate itemTemplate = null;
            if (base.ItemTemplate != null)
            {
                itemTemplate = base.ItemTemplate;
            }
            else if (this.ItemTemplateSelector != null)
            {
                itemTemplate = this.ItemTemplateSelector.SelectTemplate(item, element);
            }

            presenter.Content = item;
            presenter.ContentTemplate = itemTemplate;
        }
    }
}

public class DataTemplateSelector
{
    public virtual DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        return null;
    }
}
小姐丶请自重 2024-08-29 11:07:41

您可以使用转换器。例如,Forecolor="{Binding MyText, Converter=ColorFromText}
您需要将颜色选择逻辑存储到转换器中。

You can use converters. Foe example, Forecolor="{Binding MyText, Converter=ColorFromText}.
You would need to store color-picking logic into the converter.

趁年轻赶紧闹 2024-08-29 11:07:41

Afaik,DataTemplateSelector 不兼容 Blend。至少我没能成功。

完全可混合的解决方案涉及一个自定义控件,该控件继承自 ListBox 并公开一个属性(一个或多个,但一个更容易),该属性将调节要应用的数据模板。然后编辑此列表框的样式并添加触发器。在触发器(属性触发器)内,您可以将数据模板更改为与触发器值匹配的数据模板。

例子:

<Style x:Key="OrientedListStyle" TargetType="{x:Type local:OrientedListBox}">
<Setter Property="ItemTemplate" Value="{DynamicResource TemplateVertical}"/>
<Setter Property="ItemsPanel" Value="{DynamicResource PanelVertical}"/>

<Style.Triggers>
    <Trigger Property="Orientation" Value="Horizontal">
        <Setter Property="ItemTemplate" Value="{DynamicResource TemplateHorizontal}"/>
        <Setter Property="ItemsPanel" Value="{DynamicResource PanelHorizontal}"/>
    </Trigger>
</Style.Triggers>

Afaik, DataTemplateSelector is not Blend-compatible. At least I couldn't make it work.

A completely Blendable solution involves one custom control, inherited from a ListBox and exposing a property (one or more, but one is easier), which would regulate, which data templates to apply. Then you edit the style for this ListBox and add triggers. Inside a trigger (Property Trigger) you change the data template to the one matching the trigger value.

Example:

<Style x:Key="OrientedListStyle" TargetType="{x:Type local:OrientedListBox}">
<Setter Property="ItemTemplate" Value="{DynamicResource TemplateVertical}"/>
<Setter Property="ItemsPanel" Value="{DynamicResource PanelVertical}"/>

<Style.Triggers>
    <Trigger Property="Orientation" Value="Horizontal">
        <Setter Property="ItemTemplate" Value="{DynamicResource TemplateHorizontal}"/>
        <Setter Property="ItemsPanel" Value="{DynamicResource PanelHorizontal}"/>
    </Trigger>
</Style.Triggers>

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文