WPF主窗口更新第二窗口列表与DataGrid的实现问题
Hey there, let's break down your problem and fix it step by step—this is a super common WPF pitfall, so you're not alone!
First, Why Your Current Setup Isn't Working
List<Laser>doesn't notify the UI of changes: When you add/remove/update lasers in yourlaserList, the second window's DataGrid has no way of knowing something changed because regularList<T>doesn't implementINotifyCollectionChanged.- Thread exceptions from cross-thread UI updates: Serial port events run on a background thread, and WPF strictly requires all UI-related operations (including updating bound data) to happen on the main UI thread. Directly modifying the list from the serial thread triggers that exception.
Let's Fix This with Simple, Practical Changes
We'll use WPF's built-in data binding features without overcomplicating MVVM—just enough to get your windows synced and avoid thread issues.
Step 1: Update Your Laser Class to Notify Property Changes
First, make sure individual laser properties (like power, temperature) trigger UI updates when they change. Add INotifyPropertyChanged to your Laser class:
using System.ComponentModel; public class Laser : INotifyPropertyChanged { private string _name; public string Name { get => _name; set { _name = value; OnPropertyChanged(nameof(Name)); } } private int _wavelength; public int Wavelength { get => _wavelength; set { _wavelength = value; OnPropertyChanged(nameof(Wavelength)); } } private int _power; public int Power { get => _power; set { _power = value; OnPropertyChanged(nameof(Power)); } } private int _temperature; public int Temperature { get => _temperature; set { _temperature = value; OnPropertyChanged(nameof(Temperature)); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
Step 2: Use ObservableCollection Instead of List<Laser>
ObservableCollection<T> is WPF's go-to collection type for data binding—it automatically tells the UI when items are added, removed, or moved. We'll wrap this in a simple view model to share between both windows:
public class LaserViewModel : INotifyPropertyChanged { private ObservableCollection<Laser> _laserList; public ObservableCollection<Laser> LaserList { get => _laserList; set { _laserList = value; OnPropertyChanged(nameof(LaserList)); } } public LaserViewModel() { LaserList = new ObservableCollection<Laser>(); } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
Step 3: Update MainWindow to Use the ViewModel
Replace your List<Laser> with the view model, share it with the second window, and handle serial data safely on the UI thread:
using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; using System.IO.Ports; using System.ComponentModel; namespace WpfApp1 { using Screen = System.Windows.Forms.Screen; public partial class MainWindow : Window { SerialPort sp = new SerialPort(); LaserViewModel _viewModel; Window1 doorPanel; string[] ports = SerialPort.GetPortNames(); public MainWindow() { InitializeComponent(); // Initialize the view model and set it as the DataContext _viewModel = new LaserViewModel(); DataContext = _viewModel; foreach (string s in SerialPort.GetPortNames()) { dropdownList.Items.Add(s); } // Hook up serial data received event (you probably have this already) sp.DataReceived += Sp_DataReceived; } private void Sp_DataReceived(object sender, SerialDataReceivedEventArgs e) { // Read and parse serial data (replace with your actual parsing logic) string serialData = sp.ReadLine(); Laser updatedLaser = ParseSerialDataIntoLaser(serialData); // MUST update the collection on the UI thread to avoid exceptions Application.Current.Dispatcher.Invoke(() => { // Update existing laser or add a new one var existingLaser = _viewModel.LaserList.FirstOrDefault(l => l.Name == updatedLaser.Name); if (existingLaser != null) { existingLaser.Power = updatedLaser.Power; existingLaser.Temperature = updatedLaser.Temperature; // Update other properties as needed } else { _viewModel.LaserList.Add(updatedLaser); } }); } // Example parsing method—replace with your actual logic private Laser ParseSerialDataIntoLaser(string data) { // Split your serial string and map to Laser properties return new Laser { Name = "Laser1", Wavelength = 532, Power = 800, Temperature = 28 }; } private void showDoorDisplay(object sender, RoutedEventArgs e) { doorPanel = new Window1(); // Share the SAME view model with the second window—this is key for syncing doorPanel.DataContext = _viewModel; int secondScreen = 1; ShowOnMonitor(secondScreen, doorPanel); } private void ShowOnMonitor(int monitor, Window window) { var screen = ScreenHandler.GetScreen(monitor); window.WindowState = WindowState.Normal; window.Left = screen.Bounds.Left; window.Top = screen.Bounds.Top; window.Width = screen.Bounds.Width; window.Height = screen.Bounds.Height; window.Show(); } // Keep your existing connect/disconnect methods here—just use _viewModel.LaserList when updating lasers } }
Step 4: Configure Window1's DataGrid to Bind to the ViewModel
Update your Window1.xaml to bind its DataGrid directly to the shared LaserList:
<Window x:Class="WpfApp1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Door Display" Height="450" Width="800"> <Grid> <DataGrid ItemsSource="{Binding LaserList}" AutoGenerateColumns="False" IsReadOnly="True"> <DataGrid.Columns> <DataGridTextColumn Header="Laser Name" Binding="{Binding Name}"/> <DataGridTextColumn Header="Wavelength" Binding="{Binding Wavelength}"/> <DataGridTextColumn Header="Power" Binding="{Binding Power}"/> <DataGridTextColumn Header="Temperature" Binding="{Binding Temperature}"/> </DataGrid.Columns> </DataGrid> </Grid> </Window>
And simplify Window1's code-behind (no need to pass the list anymore):
public partial class Window1 : Window { public Window1() { InitializeComponent(); } }
Why This Works
ObservableCollectionautomatically tells the DataGrid when the collection changes (add/remove items), so both windows refresh instantly.- Shared ViewModel means both windows are looking at the exact same list of lasers—no need to manually sync them.
Dispatcher.Invokeensures all data updates happen on the main UI thread, eliminating those annoying cross-thread exceptions.
内容的提问来源于stack exchange,提问作者Tolga Aktas

