将自定义用户控件的 StackPanel 绑定到自定义 类 的 ObservableCollection
Binding a StackPanel of custom user controls to an ObservableCollection of custom classes
我有一个名为 NextBestActions 的 ObservableCollection'<'NextBestAction'>' 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 中的每个项目创建了一个 Card 并在 StackPanel 中可见,但是,每个 Card 的 Title 和 Description 都是空白的,不会获取它们各自的数据NextBestAction.
我觉得我已经完成了 90%。我将不胜感激任何帮助。谢谢!
UPDATE/EDIT 1:目前 NextBestActionTypeConverter 不是 called/hit。如果我从 XAML 中删除 ItemsControl.ItemTemplate 定义,则会调用 NextBestActionTypeConverter,但 destinationType 为 "string"。我正在尝试 force/set ItemsControl 以了解项目将表示为卡片。
UPDATE/EDIT 2(答案):以下是答案的片段:
// 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>
我看到你的代码中有一些潜在的问题点。
首先,您似乎没有完整的属性,在您的 NextBestAction
对象中为 Title
和 Description
定义了 get; set;
访问器方法。
如果我没记错的话,WPF 的绑定系统需要具有 get/set 访问器的完整属性,并且不会绑定到没有它们的字段。
所以
public string title, description;
应该变成
public string Title { get; set; }
public string Description { get; set; }
另一个潜在的问题可能是您没有在 Card
用户控件中绑定 Title/Description 文本框的 .Text 属性。
所以假设您没有使用像 Caliburn Micro 这样基于 .Name
属性 自动创建绑定的框架,此代码
<TextBlock Name="Title" ... />
<TextBlock Name="Description" ... />
应该是
<TextBlock Name="Title" Text="{Binding Title}" ... />
<TextBlock Name="Description" Text="{Binding Description}" ... />
此外,我很确定绑定是 case-sensitive,因此您要确保绑定的大小写(或 Name
属性,如果使用 Caliburn Micro)匹配你的财产的情况。
最后,每当我看到 UserControl
硬编码 .DataContext
时,我的脑海中就会响起警钟。用户控件不应该这样做。他们应该从任何使用该控件的代码中获取他们的 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
,当您键入 <StackPanel Background="Red" />
之类的内容时,.Color
设置为红色。我没有看到它在您当前的代码中有什么用处,建议您完全摆脱它,除非您出于某些特定原因需要它。)
我有一个名为 NextBestActions 的 ObservableCollection'<'NextBestAction'>' 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 中的每个项目创建了一个 Card 并在 StackPanel 中可见,但是,每个 Card 的 Title 和 Description 都是空白的,不会获取它们各自的数据NextBestAction.
我觉得我已经完成了 90%。我将不胜感激任何帮助。谢谢!
UPDATE/EDIT 1:目前 NextBestActionTypeConverter 不是 called/hit。如果我从 XAML 中删除 ItemsControl.ItemTemplate 定义,则会调用 NextBestActionTypeConverter,但 destinationType 为 "string"。我正在尝试 force/set ItemsControl 以了解项目将表示为卡片。
UPDATE/EDIT 2(答案):以下是答案的片段:
// 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>
我看到你的代码中有一些潜在的问题点。
首先,您似乎没有完整的属性,在您的 NextBestAction
对象中为 Title
和 Description
定义了 get; set;
访问器方法。
如果我没记错的话,WPF 的绑定系统需要具有 get/set 访问器的完整属性,并且不会绑定到没有它们的字段。
所以
public string title, description;
应该变成
public string Title { get; set; }
public string Description { get; set; }
另一个潜在的问题可能是您没有在 Card
用户控件中绑定 Title/Description 文本框的 .Text 属性。
所以假设您没有使用像 Caliburn Micro 这样基于 .Name
属性 自动创建绑定的框架,此代码
<TextBlock Name="Title" ... />
<TextBlock Name="Description" ... />
应该是
<TextBlock Name="Title" Text="{Binding Title}" ... />
<TextBlock Name="Description" Text="{Binding Description}" ... />
此外,我很确定绑定是 case-sensitive,因此您要确保绑定的大小写(或 Name
属性,如果使用 Caliburn Micro)匹配你的财产的情况。
最后,每当我看到 UserControl
硬编码 .DataContext
时,我的脑海中就会响起警钟。用户控件不应该这样做。他们应该从任何使用该控件的代码中获取他们的 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
,当您键入 <StackPanel Background="Red" />
之类的内容时,.Color
设置为红色。我没有看到它在您当前的代码中有什么用处,建议您完全摆脱它,除非您出于某些特定原因需要它。)