将itemssoource绑定到不同类内的ObservableCollection并向其中添加项的WPF MVVM



我集成了一个小型应用程序和一个控制台窗口,该窗口将信息传递给用户。

在我看来,有一个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();
}
}

我的视图模式有三个信息:

  1. 实例化MyLogicClass,它在另一个项目中结构化,并作为参考提供
  2. ConsoleOutputList被实例化为能够显示";控制台输出;在稍后的视图中
  3. 该按钮不会显示在视图中,因为它在视图中不相关,而且它可以正常工作

这里是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);
}
}

现在我有两个问题:

  1. 我在ConsoleOutputList的ctor中添加了一个新元素作为测试,以查看我的视图是否正常工作=>但不起作用
  2. 我有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。请改用ListBoxListBox是增强型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);
}
}

相关内容

  • 没有找到相关文章

最新更新