我想创建XAML聊天界面,根据它的邻居显示不同的消息。这是一个例子:
我认为ListBox控件最适合这个。我也在考虑不同的控件,例如FlowDocumentReader
,但我从未使用它们。另外我需要提一下,消息的文本应该是可选的(跨多个消息),我不知道如何使用ListBox实现这一点。
更新:主要的一点是,如果一方(在这种情况下是维京)连续发送一些消息,接口应该连接那些(使用超薄消息头而不是完整消息头)。因此,带有标题的消息外观取决于先前的消息是否由同一个人发送。
答案 0 :(得分:1)
假设您的ItemTemplate
是StackPanel
TextBlock
标题和TextBlock
消息,您可以使用MultiBinding
可见性Converter
隐藏标题为:
<TextBlock Text="{Binding UserName}">
<TextBlock.Visibility>
<MultiBinding Converter="{StaticResource headerVisibilityConverter}">
<Binding RelativeSource="{RelativeSource PreviousData}"/>
<Binding/>
</MultiBinding>
</TextBlock.Visibility>
</TextBlock>
IMultiValueConverter
逻辑类似于:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var previousMessage = values[0] as MessageItem;
var currentMessage = values[1] as MessageItem;
if ((previousMessage != null) && (currentMessage != null))
{
return previousMessage.UserName.Equals(currentMessage.UserName) ? Visibility.Hidden : Visibility.Visible;
}
return Visibility.Visible;
}
答案 1 :(得分:1)
如果您只对标题的格式感兴趣(完整或小),那么ListBox
绑定中包含PreviousData的ListView
/ ItemsControl
/ RelativeSource
就是要走的路(正如 anivas 所指出的那样)。
但是既然你补充说你想支持跨多个消息的选择,那么就我所知,这几乎排除了ItemsControl
以及从它派生的类。您将不得不使用类似FlowDocument
的内容。
不幸的是FlowDocument
没有ItemsSource
属性。有一些解决方法的例子,比如Create Flexible UIs With Flow Documents And Data Binding但是这个实现几乎让我的VS2010崩溃了(我没有调查它的原因,可能是一个简单的修复)。
我会这样做
首先,您在设计器中设计FlowDocument
的块,当您满意时,将它们移动到您设置x:Shared="False"
的资源。这将使您能够创建资源的多个实例,而不是反复使用相同的实例。然后使用ObservableCollection
作为FlowDocument
的“源”并订阅CollectionChanged
事件,并在eventhandler中获取资源的新实例,检查是否需要完整或小标题,然后将块添加到FlowDocument
。您还可以为删除等添加逻辑。
示例实现
<!-- xmlns:Collections="clr-namespace:System.Collections;assembly=mscorlib" -->
<Window.Resources>
<Collections:ArrayList x:Key="blocksTemplate" x:Shared="False">
<!-- Full Header -->
<Paragraph Name="fullHeader" Margin="5" BorderBrush="LightGray" BorderThickness="1" TextAlignment="Right">
<Figure HorizontalAnchor="ColumnLeft" BaselineAlignment="Center" Padding="0" Margin="0">
<Paragraph>
<Run Text="{Binding Sender}"/>
</Paragraph>
</Figure>
<Run Text="{Binding TimeSent, StringFormat={}{0:HH:mm:ss}}"/>
</Paragraph>
<!-- Small Header -->
<Paragraph Name="smallHeader" Margin="5" TextAlignment="Right">
<Run Text="{Binding TimeSent, StringFormat={}{0:HH:mm:ss}}"/>
</Paragraph>
<!-- Message -->
<Paragraph Margin="5">
<Run Text="{Binding Message}"/>
</Paragraph>
</Collections:ArrayList>
</Window.Resources>
<Grid>
<FlowDocumentScrollViewer>
<FlowDocument Name="flowDocument"
FontSize="14" FontFamily="Georgia"/>
</FlowDocumentScrollViewer>
</Grid>
背后的代码可以是以下几行
public ObservableCollection<ChatMessage> ChatMessages
{
get;
set;
}
public MainWindow()
{
InitializeComponent();
ChatMessages = new ObservableCollection<ChatMessage>();
ChatMessages.CollectionChanged += ChatMessages_CollectionChanged;
}
void ChatMessages_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ArrayList itemTemplate = flowDocument.TryFindResource("blocksTemplate") as ArrayList;
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (ChatMessage chatMessage in e.NewItems)
{
foreach (Block block in itemTemplate)
{
bool addBlock = true;
int index = ChatMessages.IndexOf(chatMessage);
if (block.Name == "fullHeader" &&
(index > 0 && ChatMessages[index].Sender == ChatMessages[index - 1].Sender))
{
addBlock = false;
}
else if (block.Name == "smallHeader" &&
(index == 0 || ChatMessages[index].Sender != ChatMessages[index - 1].Sender))
{
addBlock = false;
}
if (addBlock == true)
{
block.DataContext = chatMessage;
flowDocument.Blocks.Add(block);
}
}
}
}
}
在我的示例中,ChatMessage
只是
public class ChatMessage
{
public string Sender
{
get;
set;
}
public string Message
{
get;
set;
}
public DateTime TimeSent
{
get;
set;
}
}
这使您可以在消息中选择您喜欢的文字
如果您正在使用MVVM,您可以创建附加行为而不是后面的代码,我在此处制作了类似方案的示例实现:Binding a list in a FlowDocument to List<MyClass>?
此外,FlowDocument
的MSDN页面非常有用:http://msdn.microsoft.com/en-us/library/aa970909.aspx
答案 2 :(得分:0)
尝试提示伪代码:
public abstract class Message {/*Implementation*/
public enum MessageTypeEnum {Client, Viking, None};
public abstract MessageTypeEnum MessageType {get;}
}
public class ClientMessage : Message {
/*Client message concrete implementation.*/
public override MessageTypeEnum MessageType
{
get {
return MessageTypeEnum.Client;
}
}
}
public class VikingMessage : Message
{
/ *Viking message concrete implementation*/
public override MessageTypeEnum MessageType
{
get {
return MessageTypeEnum.Viking;
}
}
}
之后在绑定控件的XAML中的yor bindind代码中使用XAML属性Converter
您可以在哪里指定实现IValueConverter的类引用。这是链接
网上资源:
您可以在UI / ModelView之间转换类型。
希望这有帮助。
答案 3 :(得分:0)
我认为你不能完全通过XAML做到这一点,你需要在某处编写代码来确定每条消息之间的关系,即消息n - 1的作者是否与n相同?
我写了一个非常快速的例子,它产生了所需的输出。我的示例和生成的代码片段绝不是生产级代码,但它至少应该指向正确的方向。
首先,我首先创建了一个非常简单的对象来表示消息:
public class ChatMessage
{
public String Username { get; set; }
public String Message { get; set; }
public DateTime TimeStamp { get; set; }
public Boolean IsConcatenated { get; set; }
}
接下来,我从ObservableCollection派生了一个集合,以处理每条消息添加后的关系:
public class ChatMessageCollection : ObservableCollection<ChatMessage>
{
protected override void InsertItem(int index, ChatMessage item)
{
if (index > 0)
item.IsConcatenated = (this[index - 1].Username == item.Username);
base.InsertItem(index, item);
}
}
此集合现在可以由ViewModel公开,并绑定到视图中的ListBox。
有许多方法可以在XAML中显示模板化项目。根据您的示例界面,每个项目更改的唯一方面是标题,所以我认为它发送最多,每个ListBoxItem显示一个HeaderedContentControl,它将根据IsConcatenated值显示正确的标题:
<ListBox ItemsSource="{Binding Path=Messages}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type m:ChatMessage}">
<HeaderedContentControl Header="{Binding}">
<HeaderedContentControl.HeaderTemplateSelector>
<m:ChatHeaderTemplateSelector />
</HeaderedContentControl.HeaderTemplateSelector>
<Label Content="{Binding Path=Message}" />
</HeaderedContentControl>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
您会注意到我正在指定一个HeaderTemplateSelector,它负责在两个标题模板之一中进行选择:
public sealed class ChatHeaderTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var chatItem = item as ChatMessage;
if (chatItem.IsConcatenated)
return ((FrameworkElement)container).FindResource("CompactHeader") as DataTemplate;
return ((FrameworkElement)container).FindResource("FullHeader") as DataTemplate;
}
}
最后,这里有两个标题模板,它们被定义为视图的资源:
<DataTemplate x:Key="FullHeader">
<Border
Background="Lavender"
BorderBrush="Purple"
BorderThickness="1"
CornerRadius="4"
Padding="2"
>
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="{Binding Path=Username}" />
<TextBlock DockPanel.Dock="Right" HorizontalAlignment="Right" Text="{Binding Path=TimeStamp, StringFormat='{}{0:HH:mm:ss}'}" />
</DockPanel>
</Border>
</DataTemplate>
<DataTemplate x:Key="CompactHeader">
<Border
Background="Lavender"
BorderBrush="Purple"
BorderThickness="1"
CornerRadius="4"
HorizontalAlignment="Right"
Padding="2"
>
<DockPanel>
<TextBlock DockPanel.Dock="Right" HorizontalAlignment="Right" Text="{Binding Path=TimeStamp, StringFormat='{}{0:HH:mm:ss}'}" />
</DockPanel>
</Border>
</DataTemplate>
同样,这个例子并不完美,可能只是其中一个有效,但至少它应该指向正确的方向。