好的。忘记winforms。由于缺乏 UI 虚拟化和硬件渲染,它无用、已弃用、丑陋、不允许自定义并且是 Slow as Hell。
这是我对你所描述的看法:
<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>
后面的代码:
public partial class ThreeColumnChatSample : Window
{
public ObservableCollection<ChatEntry> LogEntries { get; set; }
private string TestData = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum";
private List<string> words;
private int maxword;
public Random random { get; set; }
public ThreeColumnChatSample()
{
InitializeComponent();
random = new Random();
words = TestData.Split(' ').ToList();
maxword = words.Count - 1;
DataContext = LogEntries = new ObservableCollection<ChatEntry>();
Enumerable.Range(0, 100)
.ToList()
.ForEach(x => LogEntries.Add(GetRandomEntry()));
}
private ChatEntry GetRandomEntry()
{
return new ChatEntry()
{
DateTime = DateTime.Now,
Sender = words[random.Next(0, maxword)],
Content = GetFlowDocumentString(string.Join(" ",Enumerable.Range(5, random.Next(10, 50)).Select(x => words[random.Next(0, maxword)])))
};
}
private string GetFlowDocumentString(string text)
{
return "<FlowDocument xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>" +
" <Paragraph>" +
" <Run Text='" + text + "'/>" +
" </Paragraph>" +
"</FlowDocument>";
}
}
数据项:
public class ChatEntry:PropertyChangedBase
{
public DateTime DateTime { get; set; }
private string _content;
public string Content
{
get { return _content; }
set
{
_content = value;
OnPropertyChanged("Content");
}
}
public string Sender { get; set; }
}
PropertyChangedBase(MVVM 助手类):
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
Application.Current.Dispatcher.BeginInvoke((Action) (() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}
结果:
- 我使用了来自this post 的
FlowDocumentToXAMLConverter
- 第三列中的丰富内容显示在
FlowDocumentViewer 中,但您可以将其更改为使用链接帖子中的可绑定RichTextBox。
- 您可以通过单击并拖动标题边缘来调整列的大小。
- WPF 具有内置的 UI 虚拟化,这意味着如果有很多行,您的应用程序也不会严重滞后。
- 您可以实施here 描述的解决方案,在调整包含窗口大小时调整最后一列的大小,从而实现自动换行和分辨率独立。
- 请注意,大部分代码隐藏实际上是支持示例的样板(生成随机条目等)。删除它,这将是一个非常干净的解决方案。
- WPF 岩石。只需将我的代码(连同链接帖子中的转换器)复制并粘贴到
File -> New Project -> WPF Application 中,然后自己查看结果。
编辑:
根据@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 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;
}
}
结果: