C# 编写此控件的最佳方法?

C# 编写此控件的最佳方法?,c#,.net,wpf,winforms,controls,C#,.net,Wpf,Winforms,Controls,对于我正在编写的聊天客户端,我希望创建以下控件: 它应该由三个用户可调整大小的列组成,其中可以显示任意文本,但仍然彼此对齐(正如Jeff所说的) 我已经有了一个自定义的RichTextBox,它可以显示预先格式化的文本并自动滚动到底部,但是如何创建一个具有可调整大小的列的文本框让我感到困惑(我对创建自己的控件还很陌生) 有没有关于太多人寻找什么或一般想法的建议?感谢您的帮助 一个可能的解决方案是使用一个包含三列和详细信息视图的控件,然后您将获得与所显示的WPF解决方案完全相同的结果,但使用wi

对于我正在编写的聊天客户端,我希望创建以下控件:

它应该由三个用户可调整大小的列组成,其中可以显示任意文本,但仍然彼此对齐(正如Jeff所说的)

我已经有了一个自定义的
RichTextBox
,它可以显示预先格式化的文本并自动滚动到底部,但是如何创建一个具有可调整大小的列的文本框让我感到困惑(我对创建自己的控件还很陌生)


有没有关于太多人寻找什么或一般想法的建议?感谢您的帮助

一个可能的解决方案是使用一个包含三列和详细信息视图的控件,然后您将获得与所显示的WPF解决方案完全相同的结果,但使用windows窗体

另一种解决方案是使用并创建一个包含三列的表,并为每个新事件添加一行,就像ListView控件一样


在这两种情况下,在第三列(您的消息内容所在的位置)中,使用富UI控件以获得良好的文本格式,例如

好的。忘了winforms吧。它是无用的、不推荐使用的、丑陋的,不允许定制,这是由于缺少UI虚拟化和硬件呈现

这是我对你所描述的内容的看法:

<Window x:Class="MiscSamples.ThreeColumnChatSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MiscSamples"
        Title="ThreeColumnChatSample" Height="300" Width="300">
    <Window.Resources>
        <local:FlowDocumentToXamlConverter x:Key="DocumentConverter"/>
    </Window.Resources>
    <ListView ItemsSource="{Binding}" ScrollViewer.HorizontalScrollBarVisibility="Hidden">
        <ListView.View>
            <GridView>
                <GridView.Columns>
                    <GridViewColumn DisplayMemberBinding="{Binding DateTime}"/>
                    <GridViewColumn DisplayMemberBinding="{Binding Sender}"/>
                    <GridViewColumn>
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <FlowDocumentScrollViewer Document="{Binding Content, Converter={StaticResource DocumentConverter}}"
                                                          VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView.Columns>
            </GridView>
        </ListView.View>
    </ListView>
</Window>
PropertyChangedBase(MVVM帮助程序类):

结果:

  • 我使用了来自的
    FlowDocumentToXAMLConverter
  • 第三列中的丰富内容显示在
    FlowDocumentViewer
    中,但您可以将其更改为使用链接文章中的可绑定
    RichTextBox
  • 可以通过单击并拖动标题边缘来调整列的大小
  • WPF具有内置的UI虚拟化,这意味着如果有很多行,应用程序将不会严重滞后
  • 您可以实现所述的解决方案,在调整包含窗口的大小时调整最后一列的大小,从而实现分词和独立解析
  • 请注意,大多数代码实际上是支持示例的样板(生成随机条目等)。去掉它,它将是一个非常干净的解决方案
  • WPF岩石。只需将我的代码(以及链接文章中的转换器)复制并粘贴到
    文件->新项目->WPF应用程序中即可
    ,并亲自查看结果
编辑:

根据@KingKing的请求,我修改了我的示例以模拟聊天客户端

我在上面链接的CodeProject帖子中添加了对
FsRichTextBox.dll
的引用

<Window x:Class="MiscSamples.ThreeColumnChatSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MiscSamples"
        xmlns:rtb="clr-namespace:FsWpfControls.FsRichTextBox;assembly=FsRichTextBox"
        Title="ThreeColumnChatSample" WindowState="Maximized">
    <Window.Resources>
        <local:FlowDocumentToXamlConverter x:Key="DocumentConverter"/>
    </Window.Resources>
    <Grid>

        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="300"/>
        </Grid.RowDefinitions>

        <ListView ItemsSource="{Binding ChatEntries}" ScrollViewer.HorizontalScrollBarVisibility="Hidden"
                  x:Name="ListView">
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn DisplayMemberBinding="{Binding DateTime}"/>
                        <GridViewColumn DisplayMemberBinding="{Binding Sender}"/>
                        <GridViewColumn>
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <FlowDocumentScrollViewer Document="{Binding Content, Converter={StaticResource DocumentConverter}}"
                                                          VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden"/>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>

        <GridSplitter Height="3" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>

        <DockPanel Grid.Row="1">
            <Button Content="Send" DockPanel.Dock="Right" VerticalAlignment="Bottom" Margin="2"
                    Click="Send_Click"/>

            <rtb:FsRichTextBox Document="{Binding UserInput,Converter={StaticResource DocumentConverter}, Mode=TwoWay}"
                           DockPanel.Dock="Bottom" Height="300" x:Name="InputBox"/>
        </DockPanel>
    </Grid>
</Window>
视图模型:

public class ChatViewModel:PropertyChangedBase
{
    public ObservableCollection<ChatEntry> ChatEntries { get; set; }
    private string _userInput;
    public string UserInput
    {
        get { return _userInput; }
        set
        {
            _userInput = value;
            OnPropertyChanged("UserInput");
        }
    }

    public string NickName { get; set; }

    public ChatViewModel()
    {
        ChatEntries = new ObservableCollection<ChatEntry>();
        NickName = "John Doe";
    }

    public ChatEntry AddEntry()
    {
        var entry = new ChatEntry {DateTime = DateTime.Now, Sender = NickName};
        entry.Content = UserInput;

        ChatEntries.Add(entry);

        UserInput = null;

        return entry;
    }
}
公共类ChatViewModel:PropertyChangedBase
{
公共ObservableCollection聊天记录项{get;set;}
私有字符串_userInput;
公共字符串用户输入
{
获取{return\u userInput;}
设置
{
_用户输入=值;
OnPropertyChanged(“用户输入”);
}
}
公共字符串昵称{get;set;}
公共聊天室视图模型()
{
ChatEntries=新的ObservableCollection();
昵称=“约翰·多伊”;
}
公共聊天室条目附录()
{
var entry=newchatEntry{DateTime=DateTime.Now,Sender=昵称};
entry.Content=UserInput;
ChatEntries.Add(条目);
UserInput=null;
返回条目;
}
}
结果:


这是
Winforms
中的一个解决方案。我不是Winforms专家,但这个解决方案还可以。我打赌Winforms专家可以做得比别人想象的更好。我试图解决这个问题,使第三列只包含1
RichTextBox
,但有一些问题。
HighCore的解决方案
似乎不是这样工作的。此解决方案在第三列为每个条目提供一个特定的
RichTextBox

public class ChatWindow : SplitContainer
{
    private SplitContainer innerSpliter = new SplitContainer();
    public ChatWindow()
    {
        Type type = typeof(Panel);
        type.GetProperty("DoubleBuffered", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(innerSpliter.Panel2, true, null);
        //Initialize some properties
        innerSpliter.Parent = Panel2;
        innerSpliter.Panel2.AutoScroll = true;
        innerSpliter.Dock = DockStyle.Fill;
        SplitterDistance = 50;
        innerSpliter.SplitterDistance = 10;
        BorderStyle = BorderStyle.FixedSingle;
        innerSpliter.BorderStyle = BorderStyle.FixedSingle;
        //-----------------------------            
        Panel1.BackColor = Color.White;
        innerSpliter.Panel1.BackColor = innerSpliter.Panel2.BackColor = Color.White;
    }
    bool adding;
    private Binding GetTopBinding(RichTextBox richText)
    {
        Binding bind = new Binding("Top", richText, "Location");
        bind.Format += (s, e) =>
        {
            Binding b = s as Binding;                           
            if (adding)
            {
                RichTextBox rtb = b.DataSource as RichTextBox;
                if (rtb.TextLength == 0) { e.Value = ((Point)e.Value).Y; return; }
                rtb.SuspendLayout();
                rtb.SelectionStart = 0;
                int i = rtb.SelectionFont.Height;
                int belowIndex = 0;
                while (belowIndex == 0&&i < rtb.Height-6)
                {
                    belowIndex = rtb.GetCharIndexFromPosition(new Point(1, i++));
                }                                        
                float baseLine1 = 0.75f * i; //This is approximate
                float baseLine2 = GetBaseLine(b.Control.Font, b.Control.CreateGraphics());//This is exact
                b.Control.Tag = (baseLine1 > baseLine2 ? baseLine1 - baseLine2 - 2: 0);
                e.Value = ((Point)e.Value).Y + (float)b.Control.Tag;
                rtb.ResumeLayout(false);
            }
            else e.Value = ((Point)e.Value).Y + (float)b.Control.Tag;
        };
        return bind;
    }
    private Binding GetHeightBinding(RichTextBox richText)
    {
        Binding bind = new Binding("Height", richText, "Size");
        bind.Format += (s, e) =>
        {
            Binding b = s as Binding;
            e.Value = ((Size)e.Value).Height - b.Control.Top + ((RichTextBox) b.DataSource).Top;
        };
        return bind;
    }
    private Binding GetWidthBinding(Panel panel)
    {
        Binding bind = new Binding("Width", panel, "Size");
        bind.Format += (s, e) =>
        {                
            e.Value = ((Size)e.Value).Width;
        };
        return bind;
    }
    public void AddItem(string first, string second, string third)
    {
        adding = true;            
        RichTextBox richText = new RichTextBox();
        innerSpliter.Panel2.SuspendLayout();
        Panel1.SuspendLayout();
        innerSpliter.Panel1.SuspendLayout();

        richText.Dock = DockStyle.Top;
        richText.Width = innerSpliter.Panel2.Width;            
        richText.ContentsResized += ContentsResized;                               
        richText.BorderStyle = BorderStyle.None;
        Label lbl = new Label() { Text = first, AutoSize = false, ForeColor = Color.BlueViolet};            
        lbl.DataBindings.Add(GetHeightBinding(richText));                      
        lbl.DataBindings.Add(GetTopBinding(richText));            
        lbl.DataBindings.Add(GetWidthBinding(Panel1));
        lbl.Parent = Panel1;            
        lbl = new Label() { Text = second,  AutoSize = false, ForeColor = Color.BlueViolet };            
        lbl.DataBindings.Add(GetHeightBinding(richText));            
        lbl.DataBindings.Add(GetTopBinding(richText));            
        lbl.DataBindings.Add(GetWidthBinding(innerSpliter.Panel1));
        lbl.Parent = innerSpliter.Panel1;            
        richText.Visible = false;
        richText.Parent = innerSpliter.Panel2;
        richText.Visible = true;
        richText.Rtf = third;            
        richText.BringToFront();             
        innerSpliter.Panel1.ResumeLayout(true);
        innerSpliter.Panel2.ResumeLayout(true);
        Panel1.ResumeLayout(true);
        innerSpliter.Panel2.ScrollControlIntoView(innerSpliter.Panel2.Controls[0]);
        adding = false;
    }
    private void ContentsResized(object sender, ContentsResizedEventArgs e)
    {
        ((RichTextBox)sender).Height = e.NewRectangle.Height + 6;
    }
    private float GetBaseLine(Font font, Graphics g)
    {
        int lineSpacing = font.FontFamily.GetLineSpacing(font.Style);
        int cellAscent = font.FontFamily.GetCellAscent(font.Style);
        return font.GetHeight(g) * cellAscent / lineSpacing;
    }
}
//I provide only 1 AddItem() method, in fact it's enough because normally we don't have requirement to remove a chat line once it's typed and sent.
chatWindow.AddItem(DateTime.Now.ToString(), "User name", "Rtf text");
公共类聊天窗口:SplitContainer
{
私有SplitContainer innerSpliter=新SplitContainer();
公共聊天窗口()
{
类型=类型(面板);
type.GetProperty(“双缓冲”,System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(innerSpliter.Panel2,true,null);
//初始化一些属性
innerSpliter.Parent=Panel2;
innerSpliter.Panel2.AutoScroll=true;
innerSpliter.Dock=DockStyle.Fill;
分流距离=50;
innerSpliter.SplitterDistance=10;
BorderStyle=BorderStyle.FixedSingle;
innerSpliter.BorderStyle=BorderStyle.FixedSingle;
//-----------------------------            
Panel1.BackColor=颜色。白色;
innerSpliter.Panel1.BackColor=innerSpliter.Panel2.BackColor=Color.White;
}
布尔加法;
私有绑定GetTopBinding(RichTextBox richText)
{
绑定绑定=新绑定(“顶部”,richText,“位置”);
bind.Format+=(s,e)=>
{
绑定b=s作为绑定;
如果(添加)
{
RichTextBox rtb=b.DataSource作为RichTextBox;
如果(rtb.TextLength==0){e.Value=((点)e.Value).Y;返回;}
rtb.SuspendLayout();
rtb.SelectionStart=0;
int i=rtb.SelectionFont.Height;
int Belovindex=0;
而(Belovindex==0&&ibaseLine2?baseLine1-baseLine2-2:0);
e、 Value=((点)e.Value).Y+(浮点)b.Control.Tag;
rtb.ResumeLayout(假);
}
否则e.Value=((点)e.Value).Y+(浮点)b.Control.Tag;
<Window x:Class="MiscSamples.ThreeColumnChatSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MiscSamples"
        xmlns:rtb="clr-namespace:FsWpfControls.FsRichTextBox;assembly=FsRichTextBox"
        Title="ThreeColumnChatSample" WindowState="Maximized">
    <Window.Resources>
        <local:FlowDocumentToXamlConverter x:Key="DocumentConverter"/>
    </Window.Resources>
    <Grid>

        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="300"/>
        </Grid.RowDefinitions>

        <ListView ItemsSource="{Binding ChatEntries}" ScrollViewer.HorizontalScrollBarVisibility="Hidden"
                  x:Name="ListView">
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn DisplayMemberBinding="{Binding DateTime}"/>
                        <GridViewColumn DisplayMemberBinding="{Binding Sender}"/>
                        <GridViewColumn>
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <FlowDocumentScrollViewer Document="{Binding Content, Converter={StaticResource DocumentConverter}}"
                                                          VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden"/>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>

        <GridSplitter Height="3" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>

        <DockPanel Grid.Row="1">
            <Button Content="Send" DockPanel.Dock="Right" VerticalAlignment="Bottom" Margin="2"
                    Click="Send_Click"/>

            <rtb:FsRichTextBox Document="{Binding UserInput,Converter={StaticResource DocumentConverter}, Mode=TwoWay}"
                           DockPanel.Dock="Bottom" Height="300" x:Name="InputBox"/>
        </DockPanel>
    </Grid>
</Window>
public partial class ThreeColumnChatSample : Window
{
    public ChatViewModel ViewModel { get; set; }

    public ThreeColumnChatSample()
    {
        InitializeComponent();

        DataContext = ViewModel = new ChatViewModel();
    }

    private void Send_Click(object sender, RoutedEventArgs e)
    {
        InputBox.UpdateDocumentBindings();

        var entry = ViewModel.AddEntry();

        ListView.ScrollIntoView(entry);
    }
}
public class ChatViewModel:PropertyChangedBase
{
    public ObservableCollection<ChatEntry> ChatEntries { get; set; }
    private string _userInput;
    public string UserInput
    {
        get { return _userInput; }
        set
        {
            _userInput = value;
            OnPropertyChanged("UserInput");
        }
    }

    public string NickName { get; set; }

    public ChatViewModel()
    {
        ChatEntries = new ObservableCollection<ChatEntry>();
        NickName = "John Doe";
    }

    public ChatEntry AddEntry()
    {
        var entry = new ChatEntry {DateTime = DateTime.Now, Sender = NickName};
        entry.Content = UserInput;

        ChatEntries.Add(entry);

        UserInput = null;

        return entry;
    }
}
public class ChatWindow : SplitContainer
{
    private SplitContainer innerSpliter = new SplitContainer();
    public ChatWindow()
    {
        Type type = typeof(Panel);
        type.GetProperty("DoubleBuffered", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(innerSpliter.Panel2, true, null);
        //Initialize some properties
        innerSpliter.Parent = Panel2;
        innerSpliter.Panel2.AutoScroll = true;
        innerSpliter.Dock = DockStyle.Fill;
        SplitterDistance = 50;
        innerSpliter.SplitterDistance = 10;
        BorderStyle = BorderStyle.FixedSingle;
        innerSpliter.BorderStyle = BorderStyle.FixedSingle;
        //-----------------------------            
        Panel1.BackColor = Color.White;
        innerSpliter.Panel1.BackColor = innerSpliter.Panel2.BackColor = Color.White;
    }
    bool adding;
    private Binding GetTopBinding(RichTextBox richText)
    {
        Binding bind = new Binding("Top", richText, "Location");
        bind.Format += (s, e) =>
        {
            Binding b = s as Binding;                           
            if (adding)
            {
                RichTextBox rtb = b.DataSource as RichTextBox;
                if (rtb.TextLength == 0) { e.Value = ((Point)e.Value).Y; return; }
                rtb.SuspendLayout();
                rtb.SelectionStart = 0;
                int i = rtb.SelectionFont.Height;
                int belowIndex = 0;
                while (belowIndex == 0&&i < rtb.Height-6)
                {
                    belowIndex = rtb.GetCharIndexFromPosition(new Point(1, i++));
                }                                        
                float baseLine1 = 0.75f * i; //This is approximate
                float baseLine2 = GetBaseLine(b.Control.Font, b.Control.CreateGraphics());//This is exact
                b.Control.Tag = (baseLine1 > baseLine2 ? baseLine1 - baseLine2 - 2: 0);
                e.Value = ((Point)e.Value).Y + (float)b.Control.Tag;
                rtb.ResumeLayout(false);
            }
            else e.Value = ((Point)e.Value).Y + (float)b.Control.Tag;
        };
        return bind;
    }
    private Binding GetHeightBinding(RichTextBox richText)
    {
        Binding bind = new Binding("Height", richText, "Size");
        bind.Format += (s, e) =>
        {
            Binding b = s as Binding;
            e.Value = ((Size)e.Value).Height - b.Control.Top + ((RichTextBox) b.DataSource).Top;
        };
        return bind;
    }
    private Binding GetWidthBinding(Panel panel)
    {
        Binding bind = new Binding("Width", panel, "Size");
        bind.Format += (s, e) =>
        {                
            e.Value = ((Size)e.Value).Width;
        };
        return bind;
    }
    public void AddItem(string first, string second, string third)
    {
        adding = true;            
        RichTextBox richText = new RichTextBox();
        innerSpliter.Panel2.SuspendLayout();
        Panel1.SuspendLayout();
        innerSpliter.Panel1.SuspendLayout();

        richText.Dock = DockStyle.Top;
        richText.Width = innerSpliter.Panel2.Width;            
        richText.ContentsResized += ContentsResized;                               
        richText.BorderStyle = BorderStyle.None;
        Label lbl = new Label() { Text = first, AutoSize = false, ForeColor = Color.BlueViolet};            
        lbl.DataBindings.Add(GetHeightBinding(richText));                      
        lbl.DataBindings.Add(GetTopBinding(richText));            
        lbl.DataBindings.Add(GetWidthBinding(Panel1));
        lbl.Parent = Panel1;            
        lbl = new Label() { Text = second,  AutoSize = false, ForeColor = Color.BlueViolet };            
        lbl.DataBindings.Add(GetHeightBinding(richText));            
        lbl.DataBindings.Add(GetTopBinding(richText));            
        lbl.DataBindings.Add(GetWidthBinding(innerSpliter.Panel1));
        lbl.Parent = innerSpliter.Panel1;            
        richText.Visible = false;
        richText.Parent = innerSpliter.Panel2;
        richText.Visible = true;
        richText.Rtf = third;            
        richText.BringToFront();             
        innerSpliter.Panel1.ResumeLayout(true);
        innerSpliter.Panel2.ResumeLayout(true);
        Panel1.ResumeLayout(true);
        innerSpliter.Panel2.ScrollControlIntoView(innerSpliter.Panel2.Controls[0]);
        adding = false;
    }
    private void ContentsResized(object sender, ContentsResizedEventArgs e)
    {
        ((RichTextBox)sender).Height = e.NewRectangle.Height + 6;
    }
    private float GetBaseLine(Font font, Graphics g)
    {
        int lineSpacing = font.FontFamily.GetLineSpacing(font.Style);
        int cellAscent = font.FontFamily.GetCellAscent(font.Style);
        return font.GetHeight(g) * cellAscent / lineSpacing;
    }
}
//I provide only 1 AddItem() method, in fact it's enough because normally we don't have requirement to remove a chat line once it's typed and sent.
chatWindow.AddItem(DateTime.Now.ToString(), "User name", "Rtf text");