賞金將在 2 天內到期。此問題的答案有資格獲得 50聲望賞金。 邁克布魯諾想要獎勵一個現有的答案:
感謝您對我的問題的徹底和解釋清楚的回答!
我有一個 WPF UserControl 與BindableRichTextBox:
xmlns:controls="clr-namespace:SysadminsLV.WPF.OfficeTheme.Controls;assembly=Wpf.OfficeTheme"
.
.
.
<controls:BindableRichTextBox Background="Black"
Foreground="White"
FontFamily="Consolas"
FontSize="12"
IsReadOnly="True"
IsReadOnlyCaretVisible="True"
VerticalScrollBarVisibility="Auto"
IsUndoEnabled="False"
Document="{Binding Contents}"/>
內容由 ViewModel 屬性控制Document:
using System.Windows.Documents;
class MyViewModel : ILogServerContract
{
readonly Paragraph _paragraph;
public MyViewModel()
{
_paragraph = new Paragraph();
Contents = new FlowDocument(_paragraph);
}
public FlowDocument Contents { get; }
//Log Server Contract Write method (accessed via NetPipe)
public void WriteLine(string text, int debugLevel)
{
//figure out formatting stuff based on debug level. not important
_paragraph.Inlines.Add(new Run(text) {
//set text color
});
}
}
如您所見,RichTextBoxDocument屬性系結到Contents來自 的屬性MyViewModel。Contents反過來,該屬性是通過 NetPipes 通過方法寫入的,該WriteLine()方法是ILogServerContract介面的一部分。
我正在努力的是:
- 如何在 RichTextBox 的內容更新時引發事件,然后
- 按照這個更簡單的問題中的建議呼叫
ScrollToEnd()RichTextBox 上的方法。由于 RichTextBox 是在 XAML 而不是代碼中宣告的,所以我不知道該怎么做。
有人可以幫忙嗎?
uj5u.com熱心網友回復:
您不應該在視圖模型類中實作這種與視圖相關的邏輯。滾動邏輯必須是您控制的一部分。
此外,Run是一個純視圖類。它 extends FrameworkElement,它應該給你一個提示,如果可能的話,避免在你的視圖模型中處理這個 UI 元素。
由于您正在實作一個簡單的訊息視圖,RichTextBox因此不是正確的控制元件。TextBlock會更合適(它還支持為文本著色Inline等元素)。
現在您想要顯示多行文本,您應該基于 a 實作您的視圖,該視圖借助 a 呈現其專案。
這種方法的主要優點是性能優越得多。在顯示大量訊息的情況下,它為您提供開箱即用的 UI 虛擬化 - 它始終會平滑滾動。重物很快變得呆滯。RunListBoxTextBlockListBoxRichTextBox
由于您的視圖模型只能處理資料,因此第一步是引入資料模型,例如LogMessage及其相關型別:
日志訊息.cs
// If you plan to modify existing messages e.g. in order to append text,
// the Message property must have a set() and must raise the PropertyChanged event.
public class LogMessage : INotifyPropertyChanged
{
public LogMessage(string message, LogLevel logLevel)
{
this.Message = message;
this.LogLevel = logLevel;
}
public string Message { get; }
public LogLevel LogLevel { get; }
public bool IsNewLine { get; init; }
public event PropertyChangedEventHandler PropertyChanged;
}
日志級別.cs
public enum LogLevel
{
Default = 0,
Debug,
Info
}
主視圖模型.cs
class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<LogMessage> LogMessages { get; }
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel()
{
this.LogMessages = new ObservableCollection<LogMessage>();
WriteLine("Debug test message.", LogLevel.Debug);
WriteLine("Info test message.", LogLevel.Info);
}
// To implement Write() to avoid line breaks,
// simply append the new message text to the previous message.
public void WriteLine(string message, LogLevel logLevel)
{
var newMessage = new LogMessage(message, logLevel) { IsNewLine = true };
this.LogMessages.Add(newMessage);
}
}
Then implement the view that displays the messages. Although this example uses a UserControl, I highly recommend to create a custom control by extending Control instead:
LogLevelToBrushConverter.cs
public class LogLevelToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value switch
{
LogLevel.Debug => Brushes.Blue,
LogLevel.Info => Brushes.Gray,
_ => Brushes.Black
};
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
LogOutputBox.xaml.cs
public partial class LogOutputBox : UserControl
{
public IList<LogMessage> LogMessagesSource
{
get => (IList<LogMessage>)GetValue(LogMessagesSourceProperty);
set => SetValue(LogMessagesSourceProperty, value);
}
public static readonly DependencyProperty LogMessagesSourceProperty = DependencyProperty.Register(
"LogMessagesSource",
typeof(IList<LogMessage>),
typeof(LogOutputBox),
new PropertyMetadata(default(IList<LogMessage>), OnLogMessagesSourceChanged));
public LogOutputBox()
{
InitializeComponent();
}
private static void OnLogMessagesSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> (d as LogOutputBox).OnLogMessagesSourceChanged(e.OldValue as IList<LogMessage>, e.NewValue as IList<LogMessage>);
// Listen to CollectionChanged events
// in order to always keep the last and latest item in view.
protected virtual void OnLogMessagesSourceChanged(IList<LogMessage> oldMessages, IList<LogMessage> newMessages)
{
if (oldMessages is INotifyCollectionChanged oldObservableCollection)
{
oldObservableCollection.CollectionChanged -= OnLogMessageCollectionChanged;
}
if (newMessages is INotifyCollectionChanged newObservableCollection)
{
newObservableCollection.CollectionChanged = OnLogMessageCollectionChanged;
}
}
private void OnLogMessageCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
LogMessage item = this.LogMessagesSource.LastOrDefault();
ListBox listBox = this.Output;
Dispatcher.InvokeAsync(
() => listBox.ScrollIntoView(item),
DispatcherPriority.Background);
}
}
LogOutputBox.xaml
<UserControl>
<UserControl.Resources>
<local:LogLevelToBrushConverter x:Key="LogLevelToBrushConverter" />
</UserControl.Resources>
<ListBox x:Name="Output"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=LogMessagesSource}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:LogMessage}">
<TextBlock>
<!-- If you expect Message to change, adjust the Binding.Mode to OneWay.
Otherwise leave it as OneTime to improve performance
-->
<Run Text="{Binding Message, Mode=OneTime}"
Foreground="{Binding LogLevel, Mode=OneTime, Converter={StaticResource LogLevelToBrushConverter}}" />
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
Usage example
MainWindow.xaml
<Window>
<Window.DataContext>
<MainViewModel />
</Window.DataContext>
<LogOutputBox LogMessagesSource="{Binding LogMessages}" />
</Window>
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/456546.html
