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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 09:25:56