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



WPF-可绑定聊天视图,可选择文本

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

目前,我使用WebBrowser来显示消息。所以我有大量的HTML+JS+CSS。我想我甚至不用说它有多可怕。

你能给我指正确的方向吗?

您可以查看FlowDocument。这个类可以用于自定义类似于ItemsControl的块(段落(的外观,它也可以包含UI控件(如果您需要的话(。当然,文本选择将适用于整个文档。

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

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

这就是你可以开始的:

<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作为其内容,但我们需要我们的项目是Paragraphs。

您可以根据需要配置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或附加属性。

我认为,一个文本框应该能提供您想要的内容。你需要做造型,让它看起来像你想要的,但这里有代码: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来获得更好的格式。

最新更新