【问题标题】:WPF - Bindable chat view with selectable textWPF - 带有可选文本的可绑定聊天视图
【发布时间】:2019-04-17 23:13:47
【问题描述】:

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

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

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

你能指出正确的方向吗?

【问题讨论】:

  • 您是否正在寻找一个提供文本选择的控件?像 RichTextBox 一样?或者您想要自动触摸消息并复制到剪贴板?你能发布一些你的代码吗?
  • OP 希望能够从一个项目控制项目模板中选择文本,并通过拖动鼠标来选择另一个项目,就像在 slack 或团队中一样。跨度>
  • @ClaytonHarbich,我没有代码,因为我不知道该怎么做。这就是我在这里的原因:) Michael 是对的 - 我想要一个控件,我可以用它来将鼠标悬停在内容上以选择我需要的一切。就像任何其他信使一样。

标签: c# .net wpf user-interface


【解决方案1】:

您可以查看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 功能:样式、模板、资源等。但是,对于缺少的功能,您只需要回退到代码。但在这种情况下,请始终尽量避免视图中的代码隐藏。为此创建Behaviors 或附加属性。

【讨论】:

  • 谢谢!我会检查你的方法并回来:)
  • 这解决了问题吗?
  • @dymanoid 很好的答案,谢谢!你能再告诉我一件事吗?我希望我的消息“气泡”使其宽度适合消息文本长度。但是段落填充了整个文档的宽度。如果我将 InlineUiContainer 与 Border (或任何其他控件)之类的东西一起使用,那么我将无法正确选择和复制文本(只有 Run 中的文本是可选的)。我该怎么办?
  • @TroyPalacino 一般 - 是的,但我还有一个问题。看看我上面的评论。
  • 我不确定我在这里做错了什么,但我无法让它工作。实际设置 DataContext 时不会出现此行为。所以 Messages 总是为空。
【解决方案2】:

一个文本框应该给你你正在寻找的我认为。您将需要进行样式设置,使其看起来像您想要的,但这里是代码: XAML:

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

视图模型:

    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 来获得更好的格式。

【讨论】:

  • 请仔细阅读我的问题。列表视图不起作用。在这种情况下,我只能选择一条消息。我无法按下鼠标按钮并将其覆盖在所有消息上以将它们全部选中。我在我的问题中描述了它。
  • 更新答案。
  • 感谢您的宝贵时间,但由于许多原因,这是一个非常肮脏的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-01-28
  • 2016-10-25
  • 2011-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多