繁体   English   中英

使用 MVVM 模式在 WPF 中基于 List 动态添加用户控件

[英]Dynamically add User Controls based on List in WPF with MVVM pattern

我的应用程序有两个视图。 查看第一个视图 UpdaterMainView,您可以看到我创建了一个包含两列的 Grid。 左列被进一步拆分为 5 行。 我想要做的是从存储在 UpdaterMainViewModel, TaskList中的对象列表中填充五行。 我创建了一个名为 TaskView 的自定义用户控件,它是我希望如何显示列表、按钮和文本框信息的布局。

当我实例化 UpdaterMainViewModel 时,我的应用程序会扫描目录中的一些内容并创建要完成的任务列表。 总共只有五个可能的任务,因此我创建了五行。 如果不满足条件并且不需要运行任务 2,我不想显示 UserControl 和它下面的那个,应该向上移动。 我不想使用代码隐藏,除非它仍然符合 MVVM 的指导方针

为了测试,我将<local:TaskView Grid.Column="0" Grid.Row="0"/>添加到 UpdaterMainView 并创建了一个默认构造函数。 但是,我需要的是为 TaskList 中的每个项目添加 TaskView。 为了让它工作,我还必须使用不同的构造函数创建 UserControls。 使用 DataContext 时,它使用无参数构造函数。 我不知何故需要使用带有 enum 参数的构造函数来用每个适当的 TaskView 填充网格。

更新主视图

<UserControl x:Class="POSUpdaterGUI.View.UpdaterMainView"
             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:POSUpdaterGUI.View" xmlns:local1="clr-namespace:POSUpdaterGUI.ViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.DataContext>
        <local1:UpdaterMainViewModel/>
    </UserControl.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <Grid Grid.Row="0">
            <Grid.RowDefinitions>
                <RowDefinition Height="60" />
                <RowDefinition Height="60" />
                <RowDefinition Height="60" />
                <RowDefinition Height="60" />
                <RowDefinition Height="60" />
            </Grid.RowDefinitions>

            <local:TaskView Grid.Column="0" Grid.Row="0"/>
            <local:TaskView Grid.Column="0" Grid.Row="1"/>
            <local:TaskView Grid.Column="0" Grid.Row="2"/>
            <local:TaskView Grid.Column="0" Grid.Row="3"/>
        </Grid>

        <TextBox Grid.Column="1" Margin="10" TextWrapping="Wrap" Text="{Binding OutputText}"/>

    </Grid>
</UserControl>

更新主视图模型

using POSUpdaterLibrary;
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace POSUpdaterGUI.ViewModel
{
    public class UpdaterMainViewModel : INotifyPropertyChanged
    {
        private string outputText;

        public string OutputText
        {
            get => outputText;
            set
            {
                outputText = value;
                NotifyPropertyChanged("OutputText");
            }
        }

        public UpdateTaskList TaskList { get; set; }

        public UpdaterMainViewModel()
        {
            Log("Application Starting.");
            TaskList = new UpdateTaskList();
            TaskList.LoggerEvent += TaskList_LoggerEvent;
            TaskList.GetList(AppContext.BaseDirectory);
        }

        private void TaskList_LoggerEvent(object sender, LoggerEventArgs e)
        {
            Log(e.LogString);
        }

        private void Log(string message)
        {
            if (OutputText == null)
            {
                OutputText = DateTime.Now.ToString() + " - " + message;
                return;
            }
            OutputText += "\n" + DateTime.Now.ToString() + " - " + message;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }
}

任务视图

<UserControl x:Class="POSUpdaterGUI.View.TaskView"
         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:POSUpdaterGUI.View" xmlns:local1="clr-namespace:POSUpdaterGUI.ViewModel"
         mc:Ignorable="d" 
         d:DesignHeight="60" d:DesignWidth="500">

<UserControl.DataContext>
    <local1:TaskViewModel/>
</UserControl.DataContext>

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition  Width="60"/>
        <ColumnDefinition  Width="*"/>
    </Grid.ColumnDefinitions>

    <Button Grid.Column="0" HorizontalAlignment="Center" Margin="0,0,0,0" VerticalAlignment="Center" Height="50" Width="50" Command="{Binding RunTaskCommand}">
        <Image Source="{Binding StatusImage}"/>
    </Button>
    <Label Grid.Column="1" Content="{Binding TaskName}" HorizontalAlignment="Left" Margin="15,0,0,0" VerticalAlignment="Center"/>
</Grid>

任务视图模型:

using POSUpdaterLibrary;
using System;
using System.ComponentModel;
using System.Windows.Input;

namespace POSUpdaterGUI.ViewModel
{
    class TaskViewModel : INotifyPropertyChanged
    {
        public string TaskName { get; set; }

        public ICommand RunTaskCommand { get; set; }

        public string StatusImage { get; set; }

        public TaskViewModel()
        {
            TaskName = "undefined";
            RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
            StatusImage = STARTIMAGE;
        }

        public TaskViewModel(UpdaterConstants.TaskType taskType)
        {
            switch (taskType)
            {
                case UpdaterConstants.TaskType.KillProcesses:
                    TaskName = "Processes Killed";
                    RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
                    StatusImage = STARTIMAGE;
                    break;
                case UpdaterConstants.TaskType.FileCopy:
                    TaskName = "Files Copied";
                    RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
                    StatusImage = STARTIMAGE;
                    break;
                case UpdaterConstants.TaskType.DatabaseUpdates:
                    TaskName = "Database Updated";
                    RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
                    StatusImage = STARTIMAGE;
                    break;
                case UpdaterConstants.TaskType.COMDLLsRegistered:
                    TaskName = "COM DLLs Registered";
                    RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
                    StatusImage = STARTIMAGE;
                    break;
                case UpdaterConstants.TaskType.FileCleanup:
                    TaskName = "Files Cleaned";
                    RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
                    StatusImage = STARTIMAGE;
                    break;
                default:
                    break;
            }

        }

        public enum Status
        {
            START,
            COMPLETE,
            FAILED,
            WAIT
        }

        internal const string STARTIMAGE = "/data/start.png";
        internal const string COMPLETEIMAGE = "/data/complete.png";
        internal const string FAILEDIMAGE = "/data/failed.png";
        internal const string WAITIMAGE = "/data/wait.png";

        public event PropertyChangedEventHandler PropertyChanged;

        private void DefaultMethod(object obj)
        {
            // Shouldn't be shown. Just a hold-over
            throw new NotImplementedException();
        }
    }
}

UpdaterMainView 是应用程序的主视图,并且始终显示。 为了证明这一点,这是我的MainWindow.xaml

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:POSUpdaterGUI"
    xmlns:View="clr-namespace:POSUpdaterGUI.View" x:Class="POSUpdaterGUI.MainWindow"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Grid>
    <View:UpdaterMainView/>
</Grid>

感谢@Clemens 为我指明了正确的方向! 我不需要单独的视图。 我确实需要使用数据模板。

这是我最终得到的UpdaterMainView

<UserControl x:Class="POSUpdaterGUI.View.UpdaterMainView"
             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:POSUpdaterGUI.View" xmlns:local1="clr-namespace:POSUpdaterGUI.ViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
   
    <UserControl.DataContext>
        <local1:UpdaterMainViewModel/>
    </UserControl.DataContext>
    
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="250" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <ListBox Grid.Column="0" Margin="10" ItemsSource="{Binding TaskList}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition  Width="60"/>
                                <ColumnDefinition  Width="*"/>
                            </Grid.ColumnDefinitions>

                            <Button Grid.Column="0" HorizontalAlignment="Center" Margin="0,0,0,0" VerticalAlignment="Center" Height="50" Width="50" Command="{Binding Command}">
                                <Image Source="{Binding StatusImage}"/>
                            </Button>
                            <Label Grid.Column="1" Content="{Binding TaskName}" HorizontalAlignment="Left" Margin="15,0,0,0" VerticalAlignment="Center"/>
                        </Grid>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <TextBox Grid.Column="1" Margin="10" TextWrapping="Wrap" Text="{Binding OutputText}"/>

    </Grid>
</UserControl>

这是我最终得到的UpdaterMainViewModel

using POSUpdaterGUI.Models;
using POSUpdaterLibrary;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace POSUpdaterGUI.ViewModel
{
    public class UpdaterMainViewModel : INotifyPropertyChanged
    {
        private UpdaterLogger _logger;
        private string outputText;

        public string OutputText
        {
            get => outputText;
            set
            {
                outputText = value;
                NotifyPropertyChanged("OutputText");
            }
        }

        private List<UpdateTaskWrapper> taskList;

        public List<UpdateTaskWrapper> TaskList 
        { 
            get => taskList;
            set
            {
                taskList = value;
                NotifyPropertyChanged("TaskList");
            }
        }

        public UpdaterMainViewModel()
        {
            _logger = UpdaterLogger.Instance;
            _logger.LoggerEvent += Logger_LoggerEvent;
            Log("Application Starting.");
            TaskList = GetTaskList();
        }

        private void Logger_LoggerEvent(object sender, LoggerEventArgs e)
        {
            Log(e.LogString);
        }

        private List<UpdateTaskWrapper> GetTaskList()
        {
            UpdateTaskList list = new UpdateTaskList();
            var myList = list.GetList(AppContext.BaseDirectory);


            // Create one out of the Wrapper class.
            List<UpdateTaskWrapper> wrappedList = new List<UpdateTaskWrapper>();

            foreach (var task in myList)
            {
                wrappedList.Add(new UpdateTaskWrapper(task));
            }

            return wrappedList;
        }

        private void Log(string message)
        {
            if (OutputText == null)
            {
                OutputText = DateTime.Now.ToString() + " - " + message;
                return;
            }
            OutputText += "\n" + DateTime.Now.ToString() + " - " + message;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }
}

最重要的部分是我删除了 TaskView,添加了数据模板(在标签之间),并将 List 从自定义 UpdaterTaskList 更改为 List。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM