【问题标题】:Binding a StackPanel of custom user controls to an ObservableCollection of custom classes将自定义用户控件的 StackPanel 绑定到自定义类的 ObservableCollection
【发布时间】:2016-01-26 20:04:47
【问题描述】:

我有一个名为 NextBestActions 的 ObservableCollection'' NextBestActions,其中 NextBestAction 是:

[TypeConverter(typeof(NextBestActionTypeConverter))]
public class NextBestAction : IDisposable
{
    public bool isDismissable, dismissed, completed;
    public NextBestActionType type;
    public string title, description;

    public void Dispose()
    {
        this.Dispose();
    }

    public NextBestAction()
    {

    }

    public NextBestAction(string title, string description)
    {
        this.title = title;
        this.description = description;
    }

    public static NextBestAction Parse(Card card)
    {
        if (card == null)
        {
            return new NextBestAction();
        }

        return new NextBestAction(card.Title.Text, card.Description.Text);
    }
}

我也有自己的 UserControl,叫做 Card,其中 Card 是:

public partial class Card : UserControl
{
    public Card()
    {
        InitializeComponent();
        this.DataContext = this;
    }

    public Card(string title, string description)
    {
        InitializeComponent();
        this.DataContext = this;
        this.Title.Text = title;
        this.Description.Text = description;
    }

    public static Card Parse(NextBestAction nextBestAction)
    {
        if (nextBestAction == null)
        {
            return new Card();
        }

        return new Card(nextBestAction.title, nextBestAction.description);
    }
}

使用以下 XAML:

<UserControl x:Class="AdvancedTeller.Card"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:AdvancedTeller"
         mc:Ignorable="d" 
         d:DesignWidth="300" Background="White" BorderBrush="#FF333333" VerticalContentAlignment="Top" Width="400">
<Grid Margin="10" VerticalAlignment="Top">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <TextBlock Name="Title" Grid.Column="1" Grid.Row="0" FontSize="18.667" Margin="3"/>
    <TextBlock Name="Description"  Grid.Column="1" Grid.Row="1" VerticalAlignment="Top" TextWrapping="Wrap" Margin="3"/>
</Grid>

最后,我为 NextBestAction 定义了一个 TypeConverter 为

public class NextBestActionTypeConverter : TypeConverter
{
    // Override CanConvertFrom to return true for Card-to-NextBestAction conversions. 
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(Card))
        {
            return true;
        }

        return base.CanConvertFrom(context, sourceType);
    }

    // Override CanConvertTo to return true for NextBestAction-to-Card conversions. 
    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof(Card))
        {
            return true;
        }

        return base.CanConvertTo(context, destinationType);
    }

    // Override ConvertFrom to convert from a Card to an instance of NextBestAction. 
    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
        Card card = value as Card;

        if (card != null)
        {
            try
            {
                return NextBestAction.Parse(card);
            }
            catch (Exception e)
            {
                throw new Exception(String.Format("Cannot convert '{0}' ({1}) because {2}", value, value.GetType(), e.Message), e);
            }
        }

        return base.ConvertFrom(context, culture, value);
    }

    // Override ConvertTo to convert from an instance of NextBestAction to Card. 
    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == null)
        {
            throw new ArgumentNullException("destinationType");
        }

        //Convert Complex to a string in a standard format.
        NextBestAction nextBestAction = value as NextBestAction;

        if (nextBestAction != null && this.CanConvertTo(context, destinationType) && destinationType == typeof(Card))
        {
            try
            {
                return Card.Parse(nextBestAction);
            }
            catch (Exception e)
            {
                throw new Exception(String.Format("Cannot convert '{0}' ({1}) because {2}", value, value.GetType(), e.Message), e);
            }
        }

        return base.ConvertTo(context, culture, value, destinationType);
    }
}

我正在尝试将 NextBestActions 绑定到 StackPanel,并强制 NextBestActions 在 UI 中显示为卡片。

到目前为止,我已经明白我至少需要这个

<StackPanel Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" Margin="50" >
    <ItemsControl Name="NextBestActionItems" ItemsSource="{Binding NextBestActions}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <AdvancedTeller:Card />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</StackPanel>

代码编译并运行没有任何问题,并且为 ObservableCollection 中的每个项目创建并在 StackPanel 中可见的卡片,但是,每个卡片的标题和描述都是空白的,并且不会获取其各自 NextBestAction 的数据。

我觉得我已经完成了 90% 的工作。我将不胜感激任何帮助。谢谢!

更新/编辑 1:目前未调用/命中 NextBestActionTypeConverter。如果我从 XAML 中删除 ItemsControl.ItemTemplate 定义,则调用 NextBestActionTypeConverter,但 destinationType 为“字符串”。我正在尝试强制/设置 ItemsControl 以了解项目将被表示为卡片。

更新/编辑 2(答案):这是答案的 sn-ps:

// Override ConvertTo to convert from an instance of NextBestAction to Card. 
    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == null)
        {
            throw new ArgumentNullException("destinationType");
        }

        //Convert Complex to a string in a standard format.
        NextBestAction nextBestAction = value as NextBestAction;

        if (nextBestAction != null && this.CanConvertTo(context, destinationType) && destinationType == typeof(Card))
        {
            try
            {
                return new Card();
            }
            catch (Exception e)
            {
                throw new Exception(String.Format("Cannot convert '{0}' ({1}) because {2}", value, value.GetType(), e.Message), e);
            }
        }

        return base.ConvertTo(context, culture, value, destinationType);
    }

public partial class Card : UserControl
{
    public Card()
    {
        InitializeComponent();
    }
}

<UserControl x:Class="AdvancedTeller.Card"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:AdvancedTeller"
         mc:Ignorable="d" 
         d:DesignWidth="300" Background="White" BorderBrush="#FF333333" VerticalContentAlignment="Top" Width="400">
<Grid Margin="10" VerticalAlignment="Top">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <TextBlock Name="Title" Grid.Column="1" Grid.Row="0" FontSize="18.667" Margin="3" Text="{Binding Title}"/>
    <TextBlock Name="Description"  Grid.Column="1" Grid.Row="1" VerticalAlignment="Top" TextWrapping="Wrap" Margin="3" Text="{Binding Description}"/>
</Grid>

【问题讨论】:

  • 我注意到Card UserControl 中的标题和说明文本框没有将它们的文本绑定到任何东西。这是故意的(例如使用 Caiburn Micro)还是疏忽?
  • 可能是疏忽。我想我可以在卡片的构造函数中设置标题和描述的文本,这将由 TypeConverter 调用。但是,永远不会调用 TypeConverter。如果我删除 XAML 中的 ItemTemplate,则调用 TypeConverter,但目标类型为“字符串”。我希望 ItemsControl 使用目标类型“Card”调用 TypeConverter

标签: c# wpf binding observablecollection stackpanel


【解决方案1】:

我可以看到您的代码中有一些潜在的问题点。

首先,您似乎没有完整的属性,get; set; 访问器方法是在您的 NextBestAction 对象中为 TitleDescription 定义的。

如果我没记错的话,WPF 的绑定系统需要带有 get/set 访问器的完整属性,并且不会绑定到没有它们的字段。

所以

public string title, description;

应该变成

public string Title { get; set; }
public string Description { get; set; }

另一个潜在问题可能是您没有在Card UserControl 中绑定标题/描述文本框的 .Text 属性。

因此,假设您没有使用基于 .Name 属性(如 Caliburn Micro)自动创建绑定的框架,此代码

<TextBlock Name="Title" ... />
<TextBlock Name="Description" ... />

应该是

<TextBlock Name="Title" Text="{Binding Title}" ... />
<TextBlock Name="Description" Text="{Binding Description}" ... />

另外,我很确定绑定是区分大小写的,因此您要确保您的 Bindings(或 Name 属性,如果使用 Caliburn Micro)的大小写与您的 Properties 的大小写匹配。

最后,每当我看到UserControl 硬编码它是.DataContext,我的脑海中就会响起警钟。 UserControls 不应该这样做。他们应该从使用控件的任何代码中获取他们的 DataContext,而不应该创建自己的。删除以下对Card UserControl 中的.DataContext 进行硬编码的代码行,然后它将使用NextBestActions 集合中的任何值。

public Card()
{
    InitializeComponent();
    this.DataContext = this; // Bad!
}

public Card(string title, string description)
{
    InitializeComponent();
    this.DataContext = this; // Bad!
    this.Title.Text = title; // Should be replaced with bindings as above
    this.Description.Text = description; // Should be replaced with bindings as above
}

(作为旁注,我不知道你在用那个 TypeConverter 做什么:) 它通常用于将一种类型转换为另一种类型,例如将字符串 "Red" 更改为 SolidColorBrush 和 @987654341当您输入&lt;StackPanel Background="Red" /&gt; 之类的内容时,@ 设置为红色。我看不出它与您当前的代码有什么用,建议您完全摆脱它,除非您出于某种特定原因需要它。)

【讨论】:

  • 我明白你在说什么,我知道你为什么这么说,我做了你的改变,它完美地工作!但我仍然不完全理解为什么这些更改会编译。如果 Card 的构造函数甚至不需要接受它们,那么 Card 如何处理来自 TypeConverter 的传入 Title 和 Description?
  • @MichaelDeLuca WPF 应用程序有两层:UI 和数据 (DataContext)。绑定用于将数据从数据层拉入 UI 层。默认情况下,数据层从父控件继承,除非您通过设置 .DataContext 属性另行指定,就像您在代码中所做的那样。我经常从 WPF 初学者那里看到这个错误,所以我写了一篇博客文章链接到 SO - What is this "DataContext" you speak of?。请随时查看以获取更多详细信息:)
  • @MichaelDeLuca 至于 TypeConverter,一开始我什至不知道它是什么,除非您使用 WPF 进行更高级的操作,否则通常不会使用它。当你写&lt;StackPanel Background="Red" /&gt;之类的东西时,实际处理的更像var s = new StackPanel(); s.Background = "Red";,但是这里的问题是.BackgroundBrush属性,而"Red"是一个字符串。所以使用 TypeConverter 自动将字符串转换为 Brush 对象。
  • 我的意思是,如果您查看我的编辑,其中包含使这项工作的修改后的 sn-ps,为什么它甚至 工作?我是否不必根据基础类是什么以及拥有什么来定义一个解析器来设置卡片的标题和描述以及卡片的任何其他参数?比如说 NextBestAction 和 NextBestProduct 都有 Title 和 Description,但是我希望一个用红色背景卡表示,另一个用绿色背景卡表示,是 Parser 或特殊 Card 构造函数,不需要设置这些参数?
  • @MichaelDeLuca 当您设置 ItemsSource 时,它​​告诉 WPF 循环遍历集合并为每个项目创建一个 &lt;ContentPresenter&gt;,并将 .Content 属性设置为对象。因此 WPF 尝试绘制对象,在本例中为 NextBestAction。这可能就是在您未指定 DataTemplate 时使用 TypeConverter 的原因 - 它试图将 NextBestAction 转换为字符串,以便将其呈现给 UI。修复 UserControl 中的绑定是一个不同的问题 - 如果您在阅读了 .DataContext 的更多内容后仍有疑问,请告诉我 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多