如何在WPF Grid中创建动态行并实现多视图数据复用
解决WPF磁贴视图仅显示单条数据的问题
我有一个WPF应用,可通过数据库查询加载网格表格,该功能运行正常。想要添加一种磁贴布局(每行包含两个数据项的样式)。两种视图是主窗口中的独立页面,已通过主窗口设置数据上下文解决了页面数据上下文的问题,切换视图时希望复用已有数据,无需重新查询数据库。目前网格视图正常,但磁贴视图仅显示一条记录,需要基于数据上下文动态构建行。
现有代码
主窗口代码
public MainWindow() { DataContext = (new MovieViewModel()).GetMoviesAsync().Result; InitializeComponent(); MovieList.DataContext = DataContext; MainFrame.Content = MovieList; } private void ListView_Click(object sender, RoutedEventArgs e) { MovieList.DataContext = DataContext; MainFrame.Content = MovieList; } private void TileView_Click(object sender, RoutedEventArgs e) { MovieGrid.DataContext = DataContext; MainFrame.Content = MovieGrid; }
磁贴页面XAML代码
<Page x:Class="MovieManager.Pages.MovieGrid" 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:local="clr-namespace:MovieManager.ViewModels" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" Title="MovieGrid"> <Page.DataContext> <local:MovieViewModel/> </Page.DataContext> <Grid Name="MovieTileLayout" ShowGridLines="False"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <TextBox Name="TxtMovieName" Text="{Binding Title}" Grid.Column="0" Grid.Row="0"/> <TextBox Name="TxtMovieYear" Text="{Binding ReleaseDate}" Grid.Column="1" Grid.Row="0"/> </Grid> </Page>
ViewModel代码
using MovieManager.Models; using MovieManager.Repository; using System.ComponentModel; namespace MovieManager.ViewModels { public class MovieViewModel : INotifyPropertyChanged { private int movieId { get; set; } private int groupId { get; set; } private string title { get; set; } private string description { get; set; } private string[] tags { get; set; } private string coverImage { get; set; } private DateOnly releaseDate { get; set; } private string omdb { get; set; } private string groupName { get; set; } public int MovieId { get => movieId; set { if (movieId != value) { movieId = value; RaisePropertyChanged("MovieId"); } } } public int GroupId { get => groupId; set { if(groupId != value) { groupId = value; RaisePropertyChanged("GroupId"); } } } public string Title { get => title; set { if (title != value) { title = value; RaisePropertyChanged("Title"); } } } public string Description { get => description; set { if (description != value) { description = value; RaisePropertyChanged("Description"); } } } public string[] Tags { get => tags; set { if (tags != value) { tags = value; RaisePropertyChanged("Tags"); } } } public string CoverImage { get => coverImage; set { if(coverImage != value) { coverImage = value; RaisePropertyChanged("CoverImage"); } } } public DateOnly ReleaseDate { get => releaseDate; set { if(releaseDate != value) { releaseDate = value; RaisePropertyChanged("ReleaseDate"); } } } public string OMDB { get => omdb; set { if(OMDB != value) { omdb = value; RaisePropertyChanged("OMDB"); } } } public string GroupName { get => groupName; set { if(groupName != value) { groupName = value; RaisePropertyChanged("GroupName"); } } } public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } public async Task<List<Movie>> GetMoviesAsync() { var repository = new MovieRepository(); return await repository.GetAllMoviesAsync(); } } }
解决方案
1. 重构ViewModel结构
当前MovieViewModel是单条电影数据的模型,需要拆分出集合型ViewModel来管理所有电影数据:
using MovieManager.Models; using MovieManager.Repository; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; namespace MovieManager.ViewModels { // 单条电影数据的ViewModel public class MovieItemViewModel : INotifyPropertyChanged { private int movieId { get; set; } private int groupId { get; set; } private string title { get; set; } private DateOnly releaseDate { get; set; } // 保留原有其他属性及RaisePropertyChanged逻辑 public int MovieId { get => movieId; set { if (movieId != value) { movieId = value; RaisePropertyChanged(nameof(MovieId)); } } } public string Title { get => title; set { if (title != value) { title = value; RaisePropertyChanged(nameof(Title)); } } } public DateOnly ReleaseDate { get => releaseDate; set { if(releaseDate != value) { releaseDate = value; RaisePropertyChanged(nameof(ReleaseDate)); } } } // 其他属性省略,保持原有实现 public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } } // 电影集合的ViewModel public class MovieCollectionViewModel : INotifyPropertyChanged { private ObservableCollection<MovieItemViewModel> _movies; public ObservableCollection<MovieItemViewModel> Movies { get => _movies; set { _movies = value; RaisePropertyChanged(nameof(Movies)); } } public async Task LoadMoviesAsync() { var repository = new MovieRepository(); var movieModels = await repository.GetAllMoviesAsync(); Movies = new ObservableCollection<MovieItemViewModel>( movieModels.Select(m => new MovieItemViewModel { MovieId = m.MovieId, Title = m.Title, ReleaseDate = m.ReleaseDate, // 映射其他属性 }) ); } public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); } } }
2. 修改主窗口初始化逻辑
使用集合ViewModel初始化,避免异步调用阻塞:
public MainWindow() { InitializeComponent(); var vm = new MovieCollectionViewModel(); vm.LoadMoviesAsync().Wait(); DataContext = vm; MainFrame.Content = MovieList; } // 切换视图无需重复设置DataContext private void ListView_Click(object sender, RoutedEventArgs e) { MainFrame.Content = MovieList; } private void TileView_Click(object sender, RoutedEventArgs e) { MainFrame.Content = MovieGrid; }
3. 重构磁贴页面XAML
用ItemsControl配合UniformGrid实现自动每行2个磁贴的布局,移除页面内的DataContext声明:
<Page x:Class="MovieManager.Pages.MovieGrid" 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" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" Title="MovieGrid"> <ItemsControl ItemsSource="{Binding Movies}"> <!-- 设置每行显示2个项 --> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <UniformGrid Columns="2" Margin="5"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <!-- 单个磁贴模板 --> <ItemsControl.ItemTemplate> <DataTemplate> <Border BorderThickness="1" BorderBrush="#EEE" Padding="8" Margin="4"> <StackPanel> <TextBlock Text="{Binding Title}" FontSize="14" FontWeight="Bold"/> <TextBlock Text="{Binding ReleaseDate}" FontSize="12" Foreground="#666" Margin="0,4,0,0"/> <!-- 可添加更多磁贴内容 --> </StackPanel> </Border> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Page>
关键说明
- 用
ObservableCollection替代List,确保数据变更时UI自动更新 - 分离单条数据和集合ViewModel,符合MVVM设计规范
UniformGrid的Columns="2"属性自动实现每行两个磁贴的布局,无需手动创建行- 移除页面内的DataContext声明,直接继承主窗口的集合ViewModel,保证数据复用
内容的提问来源于stack exchange,提问作者USMC6072
相关产品推荐
相关产品推荐

