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

js81xvg6  于 2023-09-28  发布在  其他
关注(0)|答案(1)|浏览(112)

我已经集成了一个小型应用程序和一个控制台窗口,该窗口将信息传递给用户。
在我看来,有一个ItemControl有一个TextBlock作为模板,显示“ConsoleText”与所需的颜色“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,它在另一个项目中结构化,并作为引用给出。

  1. ConsoleOutputList被示例化,以便稍后能够在视图中显示“ConsoleOutput”。
    1.该按钮不会显示在视图中,因为它在视图中不相关,并且可以正常工作。
    这里是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包含一个来自“ConsoleLine”的ObservableCollection(也在另一个项目中)。“ConsoleLine”可能在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中添加了一个新元素作为测试,以查看我的视图是否正常工作=>但不工作
1.我使用Test()方法来简单地测试向ObservableCollection添加新项,以查看它们在添加=>后是否会显示出来,当然这也不起作用
是的,我知道-我创建了两个ConsoleOutputList示例,这是不正确的(这是第二个问题的原因)。但我不知道如何做得更好,因为我需要从代码中的任何地方访问WriteToConsole()。(可能会更改为静态。但是我如何解决第一个问题,以及它如何处理静态属性,并在视图中显示它们。
更新:即使我将所有内容都更改为静态,“测试”行也会显示为绿色,但之后添加的所有内容都不会显示在GUI中:Visual Studio

6tr1vspr

6tr1vspr1#

在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);
  }
}

MainWindow.xaml.cs

public partial class MainWindow : Window
{
  public MainWindow(MainWindowViewModel mainWindowViewModel)
  {
    InitializeComponent();

    this.DataContext = mainWindowViewModel;
  }
}

App.xaml

<Application Startup="App_OnStartup">
</Application>

App.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 Package 成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));

或者配置绑定引擎将集合的CollectionChanged事件封送到调度器线程。
由于关键对象是一个用作绑定源的集合,因此我建议通过调用静态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);
  }
}

相关问题