我集成了一个小型应用程序和一个控制台窗口,该窗口将信息传递给用户。
在我看来,有一个ItemControl具有作为模板的TextBlock;控制台文本";具有期望的颜色";FontColour":
<Window x:Class="MyApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:helper="clr-namespace:MyApplication.Helpers"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="800" MinHeight="600" MinWidth="800">
<Grid>
<DockPanel Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="3" Grid.RowSpan="4" Background="Black">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" helper:ScrollViewerExtension.AutoScroll="True">
<ItemsControl ItemsSource="{Binding logger.ConsoleOutput}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ConsoleText}" Foreground="{Binding FontColour}" FontSize="14"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
</Grid>
</Window>
我的代码隐藏文件只包含DataContext:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
我的视图模式有三个信息:
- 实例化MyLogicClass,它在另一个项目中结构化,并作为参考提供
- ConsoleOutputList被实例化为能够显示";控制台输出;在稍后的视图中
- 该按钮不会显示在视图中,因为它在视图中不相关,而且它可以正常工作
这里是ViewModel:
public class MainWindowViewModel : ObservableObject
{
MyLogicClass myLogicClass = new MyLogicClass(null);
ConsoleOutputList logger = new ConsoleOutputList();
public MainWindowViewModel()
{
MyButtonCommand = new RelayCommand(o => Task.Run(() => myLogicClass.Test()));
}
public ICommand MyButtonCommand { get; }
}
ConsoleOutputList包含一个ObservableCollection;控制台线路";(也在另一个项目中(。";控制台线路";ctor中可能有一个字符串和一个SolidColorBrush(我认为这个也不相关(。
public class ConsoleOutputList : ObservableObject
{
public ConsoleOutputList()
{
ConsoleOutput = new ObservableCollection<ConsoleLine>();
// For testing purposes I add a random entry to see if the binding in general works - but this doesn`t work neither
ConsoleOutput.Add(new ConsoleLine("Test", Brushes.Green));
}
public ObservableCollection<ConsoleLine> consoleOutput { get; set; }
public ObservableCollection<ConsoleLine> ConsoleOutput
{
get
{
return consoleOutput;
}
set
{
consoleOutput = value;
}
}
//Used to add new lines to the ObservableCollection
public void WriteToConsole(object msg, SolidColorBrush fontColour)
{
ConsoleOutput.Add(new ConsoleLine(msg, fontColour));
}
}
这个类用于所有应用程序逻辑(也在另一个项目中(。作为一个测试,我在这里有一个简单添加文本的方法test((。
public class MyLogicClass
{
ConsoleOutputList Logger = new ConsoleOutputList();
public void Test()
{
Logger.WriteToConsole($"Test", Brushes.Gray);
}
}
现在我有两个问题:
- 我在ConsoleOutputList的ctor中添加了一个新元素作为测试,以查看我的视图是否正常工作=>但不起作用
- 我有Test((方法来简单地测试向ObservableCollection添加新项,看看它们在添加后是否显示=>但当然也不起作用
是的,我知道-我创建了两个ConsoleOutputList实例,但这是不正确的(这是第二个问题的原因(。但我不知道如何做得更好,因为我需要从代码中的任何地方访问WriteToConsole((。(也许改为静态?(但我该如何解决第一个问题,以及它如何处理静态属性并在视图中显示它们。
更新:即使我把所有东西都改成静态;测试";行显示为绿色,但我随后添加的所有内容都不会显示在GUI中:Visual Studio
在WPF中,您不能绑定到方法或字段。您必须绑定到公共属性(请参阅Microsoft文档:绑定源类型以了解有关支持的绑定源的详细信息(。
要解决问题1(,必须将MainWindowViewModel.logger
字段实现为公共属性。如果预期属性将发生更改,则必须引发INotifyPropertyChanged.PropertyChanged
事件。
要修复2(,必须在整个应用程序中分发ConsoleOutputList
的共享实例。将其公开为静态实例是一种解决方案,但通常不推荐使用。更好的解决方案是将一个共享实例传递给依赖于ConsoleOutputList
的每个类型的构造函数。
固定的解决方案可能如下所示:
MainWindowViewModel.cs
public class MainWindowViewModel : ObservableObject
{
public MainWindowViewModel(ConsoleOutputList logger, MyLogicClass myLogicClass)
{
this.Logger = logger;
this.MyLogicClass = myLogicClass;
this.MyButtonCommand = new RelayCommand(o => Task.Run(() => myLogicClass.Test()));
}
public ConsoleOutputList Logger { get; }
public ICommand MyButtonCommand { get; }
private MyLogicClass MyLogicClass { get; }
}
MyLogicClass.cs
public class MyLogicClass
{
private ConsoleOutputList Logger { get; }
public MyLogicClass(ConsoleOutputList logger) => this.Logger = logger;
public void Test()
{
this.Logger.WriteToConsole($"Test", Brushes.Gray);
}
}
主窗口.xaml.cs
public partial class MainWindow : Window
{
public MainWindow(MainWindowViewModel mainWindowViewModel)
{
InitializeComponent();
this.DataContext = mainWindowViewModel;
}
}
应用程序xaml
<Application Startup="App_OnStartup">
</Application>
应用程序.xaml.cs
public partial class App : Application
{
private void App_OnStartup(object sender, StartupEventArgs e)
{
/** Initialize the application with the shared instance **/
var sharedLoggerInstance = new ConsoleOutputList();
var classThatNeedsLogger = new MyLogicClass(sharedLoggerInstance);
var mainViewModel = new MainWindowViewModel(sharedLoggerInstance, classThatNeedsLogger);
var mainWindow = new MainWindow(mainViewModel);
mainWindow.Show();
}
}
也不要将ItemsControl
包装成ScrollViewer
。请改用ListBox
。ListBox
是增强型ItemsControl
,默认启用ScrollViewer
和UI虚拟化。如果您希望生成许多日志条目,那么您最终会在列表中看到许多项。如果不使用UI虚拟化,ItemsControl
将破坏GUI的性能/响应能力。
<DockPanel>
<ListBox ItemsSource="{Binding Logger.ConsoleOutput}">
<ListBox.ItemTemplate>
...
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
为了允许从后台线程或一般的非UI线程更新集合ConsoleOutput
,您可以使用Dispatcher
来更新集合(在这种情况下不推荐(:
Dispatcher.InvokeAsync(() => myCollection.Add(item));
或者将绑定引擎配置为将集合的CCD_ 14事件封送到调度器线程
由于关键对象是用作绑定源的集合,因此我建议通过调用静态BindingOperations.EnableCollectionSynchronization
方法来配置绑定引擎。在调度器线程上调用该方法是至关重要的:
ConsoleOutputList.cs
public class ConsoleOutputList : ObservableObject
{
private object SyncLock { get; }
public ConsoleOutputList()
{
this.SyncLock = new object();
this.ConsoleOutput = new ObservableCollection<ConsoleLine>();
// Configure the binding engine to marshal the CollectionChanged event
// of this collection to the UI thread to prevent cross-thread exceptions
BindingOperations.EnableCollectionSynchronization(this.ConsoleOutput, this.SyncLock);
}
}