You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF MVVM视图初始化后UI无法更新问题求助

WPF MVVM UI更新异常排查

问题场景

WPF MVVM架构下,XAML无法响应OnPropertyChanged事件:视图初始化时数据显示正常,但调用GetAll或Search更新数据后,UI无法同步刷新。已确认后台DataTable数据更新正确,但界面无变化。


相关代码

Parts.xaml

<UserControl x:Class="Page_Navigation_App.View.Parts"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:vm="clr-namespace:Page_Navigation_App.ViewModel"
             xmlns:utils="clr-namespace:Page_Navigation_App.Utilities"
             mc:Ignorable="d"
             d:DesignHeight="600"
             d:DesignWidth="622"
             HorizontalAlignment="Stretch"
             VerticalAlignment="Stretch"
             Style="{StaticResource Page_Style}">

    <UserControl.DataContext>
        <vm:PartsVM x:Name="_model"/>
    </UserControl.DataContext> 
    .....
    <Grid Margin="15">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="5" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <ListView ItemsSource="{Binding TableData}"
          SelectedItem="{Binding SelectedPart, Mode=TwoWay}"
          HorizontalAlignment="Stretch"
          VerticalAlignment="Stretch"
          Grid.Column="0"
          Margin="0,0,0,10"
          Visibility="{Binding PartsVisibility}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <Border Background="#343a40"
                    CornerRadius="10"
                    Padding="10"
                    BorderBrush="#3C3F41"
                    BorderThickness="1"
                    Margin="5"
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"
                    Grid.ColumnSpan="3"
                    Width="Auto">
                    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>
                        <TextBlock Text="Image:"
                               FontWeight="Bold"
                               Foreground="White"
                               Margin="0,0,10,0"
                               Grid.Column="0"
                               Grid.Row="0"
                               HorizontalAlignment="Stretch" />
                        <TextBlock Text="{Binding [ID], UpdateSourceTrigger = PropertyChanged}"
                               Foreground="White"
                               Margin="0,0,20,0"
                               Grid.Column="0"
                               Grid.Row="1"
                               HorizontalAlignment="Stretch" />
                        <TextBlock Text="Description:"
                               FontWeight="Bold"
                               Foreground="White"
                               Margin="0,0,10,0"
                               Grid.Column="1"
                               Grid.Row="0"
                               HorizontalAlignment="Stretch" />
                        <TextBlock Text="{Binding [Description],UpdateSourceTrigger=PropertyChanged}"
                               Foreground="White"
                               Margin="0,0,20,0"
                               Grid.Column="1"
                               Grid.Row="1"
                               HorizontalAlignment="Stretch" />
                        <TextBlock Text="Type:"
                               FontWeight="Bold"
                               Foreground="White"
                               Margin="0,0,10,0"
                               Grid.Column="2"
                               Grid.Row="0"
                               Visibility="{Binding [Type], Converter={StaticResource StringToVisibilityConverter} }"
                               HorizontalAlignment="Stretch" />
                        <TextBlock Text="{Binding [Type] , UpdateSourceTrigger=PropertyChanged}"
                               Foreground="White"
                               Margin="0,0,20,0"
                               Grid.Column="2"
                               Grid.Row="1"
                               Visibility="{Binding [Type], Converter={StaticResource StringToVisibilityConverter}}"
                               HorizontalAlignment="Stretch" />
                        <Button Content="Variants"
                            Command="{Binding DataContext.ShowVariantsCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}  , UpdateSourceTrigger=PropertyChanged}"
                            CommandParameter="{Binding}"
                            Grid.Column="3"
                            Grid.Row="0"
                            Grid.RowSpan="3"
                            VerticalAlignment="Stretch"
                            Margin="0,10,0,0"
                            HorizontalAlignment="Stretch"
                            Padding="5,2"/>
                    </Grid>
                </Border>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

Parts.xaml.cs

namespace Page_Navigation_App.View
{
    public partial class Parts : UserControl
    {
        public Parts()
        {
            InitializeComponent();
        }
    }
}

PartsVM.cs

using Microsoft.Data.SqlClient;
using Page_Navigation_App.Utilities;
using System;
using System.Collections.ObjectModel;
using System.Data;
using System.Linq;
using System.Windows;
using System.Windows.Input;

namespace Page_Navigation_App.ViewModel
{
    class PartsVM : Utilities.ViewModelBase
    {
        private readonly DBHandler _dbHandler;
        private static string TCPServer = "*****";
        private static string DBCatalog = "*****";
        private string _test;
        private SqlConnection _sqlConnection;
        private ObservableCollection<DataRow> _tableData;
        private DataRow _selectedPart;
        private ObservableCollection<Variant> _variants;
        private bool _isViewingVariants;
        private Visibility _partsVisibility;
        private Visibility _reversePartsVisibility = Visibility.Collapsed;
        private ObservableCollection<EndUser> _endUsers;
        private string tableName = "Sales.Parts";
        private readonly ObservableCollection<string> columnNames = new ObservableCollection<string> { "ID", "Description", "Type", "Comment" };
        int i = 0;

        public ObservableCollection<EndUser> EndUsers
        {
            get { return _endUsers; }
            set
            {
                _endUsers = value;
                OnPropertyChanged(nameof(EndUsers));
            }
        }
        public ObservableCollection<DataRow> TableData
        {
            get { return _tableData; }
            set { _tableData = value; OnPropertyChanged(nameof(TableData)); }
        }
        public string Test
        {
            get { return _test; }
            set { _test = value; OnPropertyChanged(nameof(Test)); }
        }
        public DataRow SelectedPart
        {
            get { return _selectedPart; }
            set { _selectedPart = value; OnPropertyChanged(); }
        }
        public ObservableCollection<Variant> Variants
        {
            get { return _variants; }
            set { _variants = value; OnPropertyChanged(); }
        }
        public bool IsViewingVariants
        {
            get { return _isViewingVariants; }
            set { _isViewingVariants = value; OnPropertyChanged(); }
        }
        public Visibility PartsVisibility
        {
            get { return _partsVisibility; }
            set { _partsVisibility = value; OnPropertyChanged(); }
        }
        public Visibility ReversePartsVisibility
        {
            get { return _reversePartsVisibility; }
            set { _reversePartsVisibility = value; OnPropertyChanged(); }
        }
        public ICommand ShowVariantsCommand { get; set; }
        public ICommand BackToPartsCommand { get; set; }

        public PartsVM()
        {
            _tableData = new ObservableCollection<DataRow>();
            SQLConnectionManager.Initialize();
            _sqlConnection = SQLConnectionManager.GetConnection();
            if (_sqlConnection == null)
            {
                throw new InvalidOperationException("Database connection is not initialized.");
            }
            _dbHandler = new DBHandler(_sqlConnection);
            GetAll();
        }

        public void GetAll()
        {
            i++;
            string query = $"SELECT * FROM {tableName};";
            var dataTable = new DataTable();
            using (var command = new SqlCommand(query, _sqlConnection))
            {
                using (var adapter = new SqlDataAdapter(command))
                {
                    adapter.Fill(dataTable);
                }
            }
            this.Test = i.ToString();
            var rows = new ObservableCollection<DataRow>(dataTable.AsEnumerable());
            TableData.Clear();
            TableData = rows;
        }
        public void Search(string searchTerm)
        {
            string escapedSearchTerm = "%" + searchTerm + "%";
            string likeClause = string.Join(" OR ", columnNames.Select(col => $"{col} LIKE @searchTerm"));
            string query = $"SELECT * FROM {tableName} WHERE {likeClause};";
            var dataTable = new DataTable();
            using (var command = new SqlCommand(query, _sqlConnection))
            {
                command.Parameters.AddWithValue("@searchTerm", escapedSearchTerm);
                using (var adapter = new SqlDataAdapter(command))
                {
                    adapter.Fill(dataTable);
                }
            }
            var rows = new ObservableCollection<DataRow>(dataTable.AsEnumerable());
            TableData.Clear();
            foreach (DataRow row in rows)
            {
                TableData.Add(row);
            }
            TableData = rows;
        }
    }
}

ViewModelBase

namespace Page_Navigation_App.Utilities
{
    class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName] string propName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
        }
    }
}

核心原因

  1. DataRow无属性变更通知:DataRow未实现INotifyPropertyChanged,即使ObservableCollection触发变更,UI也无法感知行内数据的变化;且{Binding [ID]}这种索引式绑定无法响应集合更新。
  2. 集合操作逻辑混乱:先执行TableData.Clear()再重新赋值TableData = rows,导致UI绑定的集合引用频繁切换,可能触发绑定失效。

解决方案

方案1:使用DataTable+DataView绑定(快速修复)

WPF对DataTable和DataView有原生变更通知支持:

  1. 修改ViewModel中TableData类型为DataTable:
private DataTable _tableData;
public DataTable TableData
{
    get { return _tableData; }
    set { _tableData = value; OnPropertyChanged(nameof(TableData)); }
}
  1. 简化GetAll和Search方法,直接赋值DataTable:
public void GetAll()
{
    i++;
    string query = $"SELECT * FROM {tableName};";
    var dataTable = new DataTable();
    using (var command = new SqlCommand(query, _sqlConnection))
    {
        using (var adapter = new SqlDataAdapter(command))
        {
            adapter.Fill(dataTable);
        }
    }
    this.Test = i.ToString();
    TableData = dataTable;
}

public void Search(string searchTerm)
{
    string escapedSearchTerm = "%" + searchTerm + "%";
    string likeClause = string.Join(" OR ", columnNames.Select(col => $"{col} LIKE @searchTerm"));
    string query = $"SELECT * FROM {tableName} WHERE {likeClause};";
    var dataTable = new DataTable();
    using (var command = new SqlCommand(query, _sqlConnection))
    {
        command.Parameters.AddWithValue("@searchTerm", escapedSearchTerm);
        using (var adapter = new SqlDataAdapter(command))
        {
            adapter.Fill(dataTable);
        }
    }
    TableData = dataTable;
}
  1. 修改XAML中ListView的绑定:
<ListView ItemsSource="{Binding TableData.DefaultView}"
          SelectedItem="{Binding SelectedPart, Mode=TwoWay}"
          ...>

方案2:自定义实体类(符合MVVM规范)

实现带变更通知的实体类替代DataRow:

  1. 创建Part实体:
public class Part : ViewModelBase
{
    private string _id;
    public string ID
    {
        get => _id;
        set { _id = value; OnPropertyChanged(); }
    }

    private string _description;
    public string Description
    {
        get => _description;
        set { _description = value; OnPropertyChanged(); }
    }

    private string _type;
    public string Type
    {
        get => _type;
        set { _type = value; OnPropertyChanged(); }
    }
}
  1. 修改ViewModel中TableData类型:
private ObservableCollection<Part> _tableData;
public ObservableCollection<Part> TableData
{
    get { return _tableData; }
    set { _tableData = value; OnPropertyChanged(nameof(TableData)); }
}
  1. 转换DataRow为Part对象:
public void GetAll()
{
    i++;
    string query = $"SELECT * FROM {tableName};";
    var dataTable = new DataTable();
    using (var command = new SqlCommand(query, _sqlConnection))
    {
        using (var adapter = new SqlDataAdapter(command))
        {
            adapter.Fill(dataTable);
        }
    }
    this.Test = i.ToString();
    var parts = new ObservableCollection<Part>();
    foreach (DataRow row in dataTable.Rows)
    {
        parts.Add(new Part
        {
            ID = row["ID"].ToString(),
            Description = row["Description"].ToString(),
            Type = row["Type"]?.ToString()
        });
    }
    TableData = parts;
}
  1. 修改XAML绑定为属性名:
<TextBlock Text="{Binding ID}" ... />
<TextBlock Text="{Binding Description}" ... />

额外注意事项

  • 移除XAML中不必要的UpdateSourceTrigger=PropertyChanged:仅双向绑定(如TextBox输入)需要该属性,单向显示绑定无需设置。
  • 确保ViewModelBase的INotifyPropertyChanged实现正确,所有需要更新的属性都调用OnPropertyChanged。

内容的提问来源于stack exchange,提问作者Python mail

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 08:42:04