WPF-具有可选文本的可绑定聊天视图

时间:2018-11-14 22:10:31

标签: c# .net wpf user-interface

WPF-具有可选文本的可绑定聊天视图

我想使用WPF创建一个简单的文本聊天应用程序。当然,用户应该能够选择并复制文本。 它非常易于使用,例如,将ListSource与ItemsSource绑定到消息。外观可以调整,但是主要问题是文本选择。只能在一个控件(一条消息)中选择文本。

此刻,我使用WebBrowser来显示消息。所以我有大量的HTML + JS + CSS。我想我什至不必说这有多可怕。

您能指出我正确的方向吗?

2 个答案:

答案 0 :(得分:3)

您可以看看FlowDocument。此类可用于自定义类似于ItemsControl的块(段落)的外观,它也可以包含UI控件(以备不时之需)。当然,文本选择将适用于整个文档。

不幸的是,FlowDocument不支持绑定,因此您必须为此编写一些代码。

让我给你一个例子。您可以使用Behavior命名空间中的System.Windows.InteractivityFlowDocument类创建可重用的功能扩展。

这是您可以开始的:

<FlowDocumentScrollViewer>
  <FlowDocument ColumnWidth="400">
    <i:Interaction.Behaviors>
      <myApp:ChatFlowDocumentBehavior Messages="{Binding Messages}">
        <myApp:ChatFlowDocumentBehavior.ItemTemplate>
          <DataTemplate>
            <myApp:Fragment>
              <Paragraph Background="Aqua" BorderBrush="BlueViolet" BorderThickness="1"/>
            </myApp:Fragment>
          </DataTemplate>
        </myApp:ChatFlowDocumentBehavior.ItemTemplate>
      </myApp:ChatFlowDocumentBehavior>
    </i:Interaction.Behaviors>
  </FlowDocument>
</FlowDocumentScrollViewer>

i命名空间为xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

因此我们的ChatFlowDocumentBehavior具有可绑定的Messages属性,用于显示聊天消息。另外,还有一个ItemTemplate属性,您可以在其中定义单个聊天消息的外观。

请注意Fragment类。这只是一个简单的包装器(下面的代码)。 DataTemplate类将不接受Paragraph作为其内容,但是我们需要将项目设为Paragraph

您可以根据需要配置Paragraph(例如颜色,字体,可能还有其他子项或控件等)

因此,Fragment类是一个简单的包装器:

[ContentProperty("Content")]
sealed class Fragment : FrameworkElement
{
  public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
    nameof(Content),
    typeof(FrameworkContentElement),
    typeof(Fragment));

  public FrameworkContentElement Content
  {
    get => (FrameworkContentElement)GetValue(ContentProperty);
    set => SetValue(ContentProperty, value);
  }
}

行为类具有更多代码,但并不复杂。

  sealed class ChatFlowDocumentBehavior : Behavior<FlowDocument>
  {
    // This is our dependency property for the messages
    public static readonly DependencyProperty MessagesProperty =
      DependencyProperty.Register(
        nameof(Messages),
        typeof(ObservableCollection<string>),
        typeof(ChatFlowDocumentBehavior),
        new PropertyMetadata(defaultValue: null, MessagesChanged));

    public ObservableCollection<string> Messages
    {
      get => (ObservableCollection<string>)GetValue(MessagesProperty);
      set => SetValue(MessagesProperty, value);
    }

    // This defines how our items will look like
    public DataTemplate ItemTemplate { get; set; }

    // This method will be called by the framework when the behavior attaches to flow document
    protected override void OnAttached()
    {
      RefreshMessages();
    }

    private static void MessagesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      if (!(d is ChatFlowDocumentBehavior b))
      {
        return;
      }

      if (e.OldValue is ObservableCollection<string> oldValue)
      {
        oldValue.CollectionChanged -= b.MessagesCollectionChanged;
      }

      if (e.NewValue is ObservableCollection<string> newValue)
      {
        newValue.CollectionChanged += b.MessagesCollectionChanged;
      }

      // When the binding engine updates the dependency property value,
      // update the flow doocument
      b.RefreshMessages();
    }

    private void MessagesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
      switch (e.Action)
      {
        case NotifyCollectionChangedAction.Add:
          AddNewItems(e.NewItems.OfType<string>());
          break;

        case NotifyCollectionChangedAction.Reset:
          AssociatedObject.Blocks.Clear();
          break;
      }
    }

    private void RefreshMessages()
    {
      if (AssociatedObject == null)
      {
        return;
      }

      AssociatedObject.Blocks.Clear();
      if (Messages == null)
      {
        return;
      }

      AddNewItems(Messages);
    }

    private void AddNewItems(IEnumerable<string> items)
    {
      foreach (var message in items)
      {
        // If the template was provided, create an instance from the template;
        // otherwise, create a default non-styled paragraph instance
        var newItem = (Paragraph)(ItemTemplate?.LoadContent() as Fragment)?.Content ?? new Paragraph();

        // This inserts the message text directly into the paragraph as an inline item.
        // You might want to change this logic.
        newItem.Inlines.Add(message);
        AssociatedObject.Blocks.Add(newItem);
      }
    }
  }

以此为起点,您可以扩展行为以适合您的需求。例如。添加事件处理逻辑以删除或重新排序消息,实现全面的消息模板等。

几乎总是可以使用XAML功能(样式,模板,资源等)以更少的代码来实现该功能。但是,对于缺少的功能,您只需要使用代码即可。但是在这种情况下,请始终尝试避免在视图中隐藏代码。为此创建Behavior或附加属性。

答案 1 :(得分:0)

我认为,文本框应该为您提供所需的内容。您将需要进行样式设计,使其看起来像您想要的,但是这里是代码: XAML:

<TextBox Text="{Binding AllMessages}"/>

ViewModel:

    public IEnumerable<string> Messages { get; set; }

    public string AllMessages => GetAllMessages();

    private string GetAllMessages()
    {
        var builder = new StringBuilder();
        foreach (var message in Messages)
        {
           //Add in whatever for context
           builder.AppendLine(message);
        }
        return builder.ToString();
    }       

您可能希望使用RichTextBox以获得更好的格式。