简体   繁体   English

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

[英]WPF - Bindable chat view with selectable text

WPF - Bindable chat view with selectable text WPF-具有可选文本的可绑定聊天视图

I want to create a simple text chat app using WPF. 我想使用WPF创建一个简单的文本聊天应用程序。 And of course user should be able to select and, for example, copy text. 当然,用户应该能够选择并复制文本。 It's very easy to use, for example, ListView with ItemsSource bound to messages. 它非常易于使用,例如,将ListSource与ItemsSource绑定到消息。 And appearance can be tuned, but the main problem is text selection. 外观可以调整,但是主要问题是文本选择。 It's possible to select text only in one single control (one message). 只能在一个控件(一条消息)中选择文本。

At the moment i use WebBrowser for showing messages. 目前,我使用WebBrowser来显示消息。 So i have tons of HTML+JS+CSS. 所以我有大量的HTML + JS + CSS。 I think i dont even have to say how terrible it is. 我想我什至不必说这有多可怕。

Can you please point me to right direction? 您能指出我正确的方向吗?

You could take a look at the FlowDocument for that. 您可以为此查看FlowDocument This class can be used for customizing the appearance of the blocks (paragraphs) similar to an ItemsControl , it can contain UI controls too (in case you need it). 此类可用于自定义类似于ItemsControl的块(段落)的外观,它也可以包含UI控件(以备ItemsControl )。 And of course, the text selection will work across the whole document. 当然,文本选择将适用于整个文档。

Unfortunately, the FlowDocument doesn't support bindings, so you will have to write some code for that. 不幸的是, FlowDocument不支持绑定,因此您必须为此编写一些代码。

Let me give you an example. 让我给你举个例子。 You could use a Behavior from the System.Windows.Interactivity namespace to create a reusable functionality extension for the FlowDocument class. 您可以使用System.Windows.Interactivity命名空间中的“ Behavior来为FlowDocument类创建可重用的功能扩展。

This is what you could start with: 您可以从以下内容开始:

<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>

(the i namespace is xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" ) i名称空间是xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

So there is our ChatFlowDocumentBehavior which has a bindable Messages property for displaying the chat messages. 因此,我们有一个ChatFlowDocumentBehavior ,它具有可绑定的Messages属性,用于显示聊天消息。 Also, there is an ItemTemplate property where you define how a single chat message should look like. 另外,还有一个ItemTemplate属性,您可以在其中定义单个聊天消息的外观。

Note the Fragment class. 注意Fragment类。 This is just a simple wrapper (code below). 这只是一个简单的包装器(下面的代码)。 The DataTemplate class won't accept a Paragraph as its content, but we need our items to be Paragraph s. DataTemplate类将不接受Paragraph作为其内容,但是我们需要将项目设为Paragraph

You can configure that Paragraph as you wish (like colors, fonts, maybe additional child items or controls etc.) 您可以根据需要配置该Paragraph (例如颜色,字体,也许其他子项或控件等)。

So, the Fragment class is a simple wrapper: 因此, 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);
  }
}

The behavior class has a little bit more code but isn't complicated. 行为类具有更多代码,但并不复杂。

  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);
      }
    }
  }

Having this as a starting point, you can extend the behavior to suit your needs. 以此为起点,您可以扩展行为以适合您的需求。 Eg add event handling logic for removing or reordering of the messages, implement comprehensive message templates etc. 例如,添加事件处理逻辑以删除或重新排序消息,实施全面的消息模板等。

It's almost always possible to implement the functionality with as less code as possible, using XAML features: styles, templates, resources etc. However, for the missing features, you just need to fall back to the code. 使用XAML功能:样式,模板,资源等,几乎总是可以用尽可能少的代码来实现该功能。但是,对于缺少的功能,您只需要使用代码即可。 But in that case, always try to avoid code-behind in the views. 但是在这种情况下,请始终尝试避免在视图中隐藏代码。 Create Behavior s or attached properties for that. 为此创建Behavior或附加属性。

A textbox should give you what you are looking for I think. 我认为,文本框应为您提供所需的信息。 You will need to do the styling so it looks like you want but here is code: XAML: 您将需要进行样式设计,使其看起来像您想要的,但是代码如下: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();
    }       

You will probably want to use a RichTextBox for better formatting. 您可能需要使用RichTextBox以获得更好的格式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM