簡體   English   中英

WPF MVVM 將項目源綁定到不同 class 內的 ObservableCollection 並向其添加項目

[英]WPF MVVM Binding of itemssource to an ObservableCollection inside a different class and add items to it

我已經將一個小應用程序與控制台 window 集成在一起,將信息傳遞給用戶。

在 meiner view habe ich ein ItemControl 和 im Template ein TextBlock 中,welches den "ConsoleText" anzeigt in der gewünschten Farbe "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>

Mein CodeBehind beinhaltet lediglich den DataContext:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MainWindowViewModel();
    }
}

我的視圖的 ViewMode 具有三個信息:

  1. 實例化 MyLogicClass,它在另一個項目中構建,並作為參考給出。
  2. ConsoleOutputList 被實例化以便稍后能夠在視圖中顯示“ConsoleOutput”。
  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 包含來自“ConsoleLine”(也在另一個項目中)的 ObservableCollection。 “ConsoleLine”在構造函數中可能有一個字符串和一個 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()。 (也許更改為 static?)但是我如何解決第一個問題以及它如何與 static 屬性一起使用並將它們顯示在視圖中。

更新:即使我將所有內容更改為 static,“測試”行顯示為綠色,但我之后添加的所有內容都未顯示在 GUI 中: Visual Studio

在 WPF 中,您無法綁定到方法或字段。 您必須綁定到公共屬性(請參閱Microsoft Docs:綁定源類型以了解有關支持的綁定源的更多信息)。

要解決問題 1),您必須將MainWindowViewModel.logger字段實現為公共屬性。 如果預期屬性會更改,則它必須引發INotifyPropertyChanged.PropertyChanged事件。

要修復 2),您必須在整個應用程序中分發ConsoleOutputList的共享實例。 將其公開為 static 實例是一種解決方案,但通常不推薦。 更好的解決方案是將共享實例傳遞給依賴於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; }
}

我的邏輯類.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;
  }
}

申請.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));

或配置綁定引擎以將集合的CollectionChanged事件編組到調度程序線程。
由於關鍵的 object 是一個作為綁定源的集合,我建議通過調用 static BindingOperations.EnableCollectionSynchronization方法來配置綁定引擎。 必須在調度程序線程上調用該方法:

控制台輸出列表.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);
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM