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