Skip to content

WinUI 3 C++/WinRT 高级数据绑定与 MVVM 架构实践(第一部分)

本文档作为《WinUI 3 C++/WinRT 数据绑定与 UI 更新详解》的高级补充,深入讲解 MVVM 架构实现、高级绑定技巧、性能优化策略和企业级开发最佳实践。本文档分为多个部分,本部分专注于 MVVM 基础架构和 ViewModel 设计模式。


一、MVVM 架构全面解析

1. MVVM 架构概念与作用

MVVM 三层架构:

View (视图层)
├── XAML 文件定义 UI 布局
├── 用户交互事件处理
└── 数据绑定到 ViewModel

ViewModel (视图模型层)
├── 业务逻辑处理
├── 数据状态管理
├── 命令和属性暴露
└── 与 Model 层交互

Model (模型层)
├── 数据实体定义
├── 业务规则实现
├── 数据访问逻辑
└── 与后端服务通信

MVVM 优势详解:

  • 关注点分离:UI 逻辑与业务逻辑完全分离
  • 可测试性:ViewModel 可独立进行单元测试
  • 可维护性:修改业务逻辑不影响 UI 代码
  • 可重用性:同一 ViewModel 可被多个 View 使用
  • 团队协作:UI 设计师和开发者可并行工作

2. WinRT/C++ 中的 MVVM 特殊考虑

类型系统要求:

cpp
// ViewModel 必须实现的基础接口
struct BaseViewModel : winrt::implements<BaseViewModel, winrt::INotifyPropertyChanged>
{
    // 所有 ViewModel 都需要的基础功能
    winrt::event<winrt::PropertyChangedEventHandler> PropertyChanged;
    
protected:
    // 属性设置辅助方法
    template<typename T>
    bool SetProperty(T& field, T const& value, hstring const& propertyName);
    
    // 通知触发方法
    void RaisePropertyChanged(hstring const& propertyName);
};

IDL 文件结构要求:

idl
// BaseViewModel.idl - 基类声明
namespace MyApp.ViewModels
{
    [default_interface]
    interface IBaseViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
    {
        // 基础 ViewModel 接口
    };
    
    runtimeclass BaseViewModel : IBaseViewModel
    {
        BaseViewModel();
    };
}

// MainViewModel.idl - 具体 ViewModel
import "BaseViewModel.idl";

namespace MyApp.ViewModels
{
    runtimeclass MainViewModel : BaseViewModel
    {
        MainViewModel();
        
        // 属性声明
        String Title;
        String UserName;
        Int32 Age;
        Boolean IsEnabled;
        
        // 集合属性
        Windows.Foundation.Collections.IObservableVector<String> Items{ get; };
        String SelectedItem;
        
        // 命令方法
        void AddItemCommand();
        void RemoveItemCommand();
        void ClearItemsCommand();
    };
}

二、BaseViewModel 基类设计与实现

1. 完整 BaseViewModel 实现

BaseViewModel.h 头文件:

cpp
#pragma once
#include "BaseViewModel.g.h"

namespace winrt::MyApp::ViewModels::implementation
{
    struct BaseViewModel : BaseViewModelT<BaseViewModel>
    {
        BaseViewModel() = default;
        virtual ~BaseViewModel() = default;

        // INotifyPropertyChanged 实现
        winrt::event<winrt::PropertyChangedEventHandler> PropertyChanged;

    protected:
        // 设置属性值并触发通知的通用方法
        template<typename T>
        bool SetProperty(T& field, T const& value, hstring const& propertyName)
        {
            // 检查值是否真正改变
            if constexpr (std::is_same_v<T, hstring>)
            {
                // 字符串比较
                if (field == value)
                    return false;
            }
            else if constexpr (std::is_floating_point_v<T>)
            {
                // 浮点数比较(考虑精度)
                if (std::abs(field - value) < std::numeric_limits<T>::epsilon())
                    return false;
            }
            else
            {
                // 其他类型直接比较
                if (field == value)
                    return false;
            }

            // 更新值并触发通知
            field = value;
            RaisePropertyChanged(propertyName);
            return true;
        }

        // 触发属性变化通知
        void RaisePropertyChanged(hstring const& propertyName)
        {
            PropertyChanged(*this, winrt::PropertyChangedEventArgs{ propertyName });
        }

        // 批量属性通知
        void RaisePropertyChanged(std::initializer_list<hstring> propertyNames)
        {
            for (auto const& name : propertyNames)
            {
                PropertyChanged(*this, winrt::PropertyChangedEventArgs{ name });
            }
        }

        // 条件属性通知
        void RaisePropertyChangedIf(bool condition, hstring const& propertyName)
        {
            if (condition)
            {
                RaisePropertyChanged(propertyName);
            }
        }
    };
}

// 工厂声明
namespace winrt::MyApp::ViewModels::factory_implementation
{
    struct BaseViewModel : BaseViewModelT<BaseViewModel, implementation::BaseViewModel>
    {
    };
}

BaseViewModel.cpp 实现文件:

cpp
#include "pch.h"
#include "BaseViewModel.h"
#include "BaseViewModel.g.cpp"

namespace winrt::MyApp::ViewModels::implementation
{
    // BaseViewModel 实现已在头文件中完成(模板方法)
    // 如需额外实现,可在此处添加
}

2. 属性设置的最佳实践

类型安全的属性设置:

cpp
// 字符串属性示例
class PersonViewModel : public BaseViewModel
{
private:
    hstring m_name{ L"" };
    int32_t m_age{ 0 };
    double m_salary{ 0.0 };
    bool m_isActive{ false };

public:
    // 字符串属性
    hstring Name() const { return m_name; }
    void Name(hstring const& value)
    {
        SetProperty(m_name, value, L"Name");
    }

    // 整数属性
    int32_t Age() const { return m_age; }
    void Age(int32_t value)
    {
        if (SetProperty(m_age, value, L"Age"))
        {
            // 年龄变化时,同时通知相关属性
            RaisePropertyChanged({ L"IsAdult", L"AgeGroup" });
        }
    }

    // 浮点数属性
    double Salary() const { return m_salary; }
    void Salary(double value)
    {
        SetProperty(m_salary, value, L"Salary");
    }

    // 布尔属性
    bool IsActive() const { return m_isActive; }
    void IsActive(bool value)
    {
        SetProperty(m_isActive, value, L"IsActive");
    }

    // 计算属性(只读)
    bool IsAdult() const { return m_age >= 18; }
    hstring AgeGroup() const
    {
        if (m_age < 18) return L"未成年";
        else if (m_age < 60) return L"成年";
        else return L"老年";
    }
};

3. 集合属性的高级处理

可观察集合的创建和管理:

cpp
class CollectionViewModel : public BaseViewModel
{
private:
    winrt::Windows::Foundation::Collections::IObservableVector<hstring> m_items{
        winrt::single_threaded_observable_vector<hstring>()
    };
    hstring m_selectedItem{ L"" };
    int32_t m_selectedIndex{ -1 };

public:
    // 集合属性
    winrt::Windows::Foundation::Collections::IObservableVector<hstring> Items() const
    {
        return m_items;
    }

    // 选中项属性
    hstring SelectedItem() const { return m_selectedItem; }
    void SelectedItem(hstring const& value)
    {
        if (SetProperty(m_selectedItem, value, L"SelectedItem"))
        {
            // 更新选中索引
            UpdateSelectedIndex();
        }
    }

    // 选中索引属性
    int32_t SelectedIndex() const { return m_selectedIndex; }
    void SelectedIndex(int32_t value)
    {
        if (SetProperty(m_selectedIndex, value, L"SelectedIndex"))
        {
            // 更新选中项
            UpdateSelectedItem();
        }
    }

    // 只读属性:项目数量
    uint32_t ItemCount() const { return m_items.Size(); }

    // 只读属性:是否有选中项
    bool HasSelection() const { return !m_selectedItem.empty(); }

private:
    void UpdateSelectedIndex()
    {
        int32_t newIndex = -1;
        for (uint32_t i = 0; i < m_items.Size(); ++i)
        {
            if (m_items.GetAt(i) == m_selectedItem)
            {
                newIndex = static_cast<int32_t>(i);
                break;
            }
        }
        
        if (m_selectedIndex != newIndex)
        {
            m_selectedIndex = newIndex;
            RaisePropertyChanged(L"SelectedIndex");
        }
    }

    void UpdateSelectedItem()
    {
        hstring newItem{ L"" };
        if (m_selectedIndex >= 0 && m_selectedIndex < static_cast<int32_t>(m_items.Size()))
        {
            newItem = m_items.GetAt(static_cast<uint32_t>(m_selectedIndex));
        }

        if (m_selectedItem != newItem)
        {
            m_selectedItem = newItem;
            RaisePropertyChanged(L"SelectedItem");
        }
    }
};

三、命令模式实现(ICommand 接口)

1. RelayCommand 基础实现

ICommand 接口的 C++/WinRT 实现:

cpp
// RelayCommand.h
#pragma once
#include "pch.h"

namespace winrt::MyApp::Commands::implementation
{
    struct RelayCommand : winrt::implements<RelayCommand, winrt::ICommand>
    {
        using ExecuteAction = std::function<void()>;
        using CanExecuteFunc = std::function<bool()>;

        // 构造函数
        RelayCommand(ExecuteAction execute)
            : m_execute(std::move(execute)), m_canExecute(nullptr) {}

        RelayCommand(ExecuteAction execute, CanExecuteFunc canExecute)
            : m_execute(std::move(execute)), m_canExecute(std::move(canExecute)) {}

        // ICommand 接口实现
        bool CanExecute(winrt::IInspectable const& parameter)
        {
            // 忽略参数,使用内部 CanExecute 函数
            return m_canExecute ? m_canExecute() : true;
        }

        void Execute(winrt::IInspectable const& parameter)
        {
            // 忽略参数,使用内部 Execute 函数
            if (CanExecute(parameter))
            {
                m_execute();
            }
        }

        // CanExecuteChanged 事件
        winrt::event<winrt::EventHandler<winrt::IInspectable>> CanExecuteChanged;

        // 手动触发 CanExecute 状态变化
        void NotifyCanExecuteChanged()
        {
            CanExecuteChanged(*this, nullptr);
        }

    private:
        ExecuteAction m_execute;
        CanExecuteFunc m_canExecute;
    };
}

2. 带参数的命令实现

支持参数的 RelayCommand:

cpp
// ParameterizedRelayCommand.h
namespace winrt::MyApp::Commands::implementation
{
    template<typename TParameter>
    struct ParameterizedRelayCommand : winrt::implements<ParameterizedRelayCommand<TParameter>, winrt::ICommand>
    {
        using ExecuteAction = std::function<void(TParameter)>;
        using CanExecuteFunc = std::function<bool(TParameter)>;

        ParameterizedRelayCommand(ExecuteAction execute)
            : m_execute(std::move(execute)), m_canExecute(nullptr) {}

        ParameterizedRelayCommand(ExecuteAction execute, CanExecuteFunc canExecute)
            : m_execute(std::move(execute)), m_canExecute(std::move(canExecute)) {}

        bool CanExecute(winrt::IInspectable const& parameter)
        {
            try
            {
                if (parameter)
                {
                    auto typedParam = winrt::unbox_value<TParameter>(parameter);
                    return m_canExecute ? m_canExecute(typedParam) : true;
                }
                return m_canExecute ? m_canExecute(TParameter{}) : true;
            }
            catch (...)
            {
                return false;
            }
        }

        void Execute(winrt::IInspectable const& parameter)
        {
            if (CanExecute(parameter))
            {
                try
                {
                    if (parameter)
                    {
                        auto typedParam = winrt::unbox_value<TParameter>(parameter);
                        m_execute(typedParam);
                    }
                    else
                    {
                        m_execute(TParameter{});
                    }
                }
                catch (...)
                {
                    // 处理参数转换错误
                }
            }
        }

        winrt::event<winrt::EventHandler<winrt::IInspectable>> CanExecuteChanged;

        void NotifyCanExecuteChanged()
        {
            CanExecuteChanged(*this, nullptr);
        }

    private:
        ExecuteAction m_execute;
        CanExecuteFunc m_canExecute;
    };
}

3. ViewModel 中的命令使用

在 ViewModel 中集成命令:

cpp
class MainViewModel : public BaseViewModel
{
private:
    // 数据
    winrt::Windows::Foundation::Collections::IObservableVector<hstring> m_items{
        winrt::single_threaded_observable_vector<hstring>()
    };
    hstring m_selectedItem{ L"" };
    hstring m_newItemText{ L"" };
    
    // 命令
    winrt::ICommand m_addCommand{ nullptr };
    winrt::ICommand m_removeCommand{ nullptr };
    winrt::ICommand m_clearCommand{ nullptr };

public:
    MainViewModel()
    {
        InitializeCommands();
        InitializeData();
    }

    // 属性
    auto Items() const { return m_items; }
    
    hstring SelectedItem() const { return m_selectedItem; }
    void SelectedItem(hstring const& value)
    {
        if (SetProperty(m_selectedItem, value, L"SelectedItem"))
        {
            // 选中项变化时,更新命令状态
            UpdateCommandStates();
        }
    }

    hstring NewItemText() const { return m_newItemText; }
    void NewItemText(hstring const& value)
    {
        if (SetProperty(m_newItemText, value, L"NewItemText"))
        {
            UpdateCommandStates();
        }
    }

    // 命令属性
    winrt::ICommand AddCommand() const { return m_addCommand; }
    winrt::ICommand RemoveCommand() const { return m_removeCommand; }
    winrt::ICommand ClearCommand() const { return m_clearCommand; }

private:
    void InitializeCommands()
    {
        // 添加命令
        m_addCommand = winrt::make<Commands::implementation::RelayCommand>(
            [this]() { ExecuteAddItem(); },
            [this]() { return CanAddItem(); }
        );

        // 删除命令
        m_removeCommand = winrt::make<Commands::implementation::RelayCommand>(
            [this]() { ExecuteRemoveItem(); },
            [this]() { return CanRemoveItem(); }
        );

        // 清空命令
        m_clearCommand = winrt::make<Commands::implementation::RelayCommand>(
            [this]() { ExecuteClearItems(); },
            [this]() { return CanClearItems(); }
        );
    }

    void InitializeData()
    {
        m_items.Append(L"初始项目 1");
        m_items.Append(L"初始项目 2");
        m_items.Append(L"初始项目 3");
    }

    // 命令执行方法
    void ExecuteAddItem()
    {
        if (!m_newItemText.empty())
        {
            m_items.Append(m_newItemText);
            NewItemText(L""); // 清空输入框
        }
    }

    void ExecuteRemoveItem()
    {
        if (!m_selectedItem.empty())
        {
            // 查找并删除选中项
            for (uint32_t i = 0; i < m_items.Size(); ++i)
            {
                if (m_items.GetAt(i) == m_selectedItem)
                {
                    m_items.RemoveAt(i);
                    SelectedItem(L""); // 清空选中项
                    break;
                }
            }
        }
    }

    void ExecuteClearItems()
    {
        m_items.Clear();
        SelectedItem(L"");
    }

    // 命令可执行性检查
    bool CanAddItem() const
    {
        return !m_newItemText.empty();
    }

    bool CanRemoveItem() const
    {
        return !m_selectedItem.empty();
    }

    bool CanClearItems() const
    {
        return m_items.Size() > 0;
    }

    // 更新所有命令状态
    void UpdateCommandStates()
    {
        if (auto addCmd = m_addCommand.try_as<Commands::implementation::RelayCommand>())
            addCmd->NotifyCanExecuteChanged();
        
        if (auto removeCmd = m_removeCommand.try_as<Commands::implementation::RelayCommand>())
            removeCmd->NotifyCanExecuteChanged();
        
        if (auto clearCmd = m_clearCommand.try_as<Commands::implementation::RelayCommand>())
            clearCmd->NotifyCanExecuteChanged();
    }
};

四、View 与 ViewModel 的绑定实现

1. XAML 中的完整绑定语法

MainWindow.xaml 布局文件:

xml
<Window x:Class="MyApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewmodels="using:MyApp.ViewModels">

    <!-- 设置 DataContext 为 ViewModel -->
    <Window.DataContext>
        <viewmodels:MainViewModel/>
    </Window.DataContext>

    <Grid Padding="20">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- 标题区域 -->
        <TextBlock Grid.Row="0"
                   Text="MVVM 数据绑定示例"
                   FontSize="24"
                   FontWeight="Bold"
                   Margin="0,0,0,20"/>

        <!-- 输入区域 -->
        <StackPanel Grid.Row="1" Orientation="Horizontal" Spacing="10" Margin="0,0,0,10">
            <TextBox PlaceholderText="输入新项目"
                     Text="{x:Bind ViewModel.NewItemText, Mode=TwoWay}"
                     Width="200"/>
            <Button Content="添加"
                    Command="{x:Bind ViewModel.AddCommand}"/>
        </StackPanel>

        <!-- 列表区域 -->
        <ListView Grid.Row="2"
                  ItemsSource="{x:Bind ViewModel.Items}"
                  SelectedItem="{x:Bind ViewModel.SelectedItem, Mode=TwoWay}"
                  Margin="0,0,0,10">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="x:String">
                    <TextBlock Text="{x:Bind}" Padding="10,5"/>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

        <!-- 操作按钮区域 -->
        <StackPanel Grid.Row="3" Orientation="Horizontal" Spacing="10" Margin="0,0,0,10">
            <Button Content="删除选中项"
                    Command="{x:Bind ViewModel.RemoveCommand}"/>
            <Button Content="清空列表"
                    Command="{x:Bind ViewModel.ClearCommand}"/>
        </StackPanel>

        <!-- 状态显示区域 -->
        <TextBlock Grid.Row="4"
                   Text="{x:Bind StatusText, Mode=OneWay}"
                   Foreground="Gray"/>
    </Grid>
</Window>

2. Code-Behind 的最小化实现

MainWindow.xaml.h 头文件:

cpp
#pragma once
#include "MainWindow.g.h"
#include "ViewModels/MainViewModel.h"

namespace winrt::MyApp::implementation
{
    struct MainWindow : MainWindowT<MainWindow>
    {
        MainWindow();

        // ViewModel 属性
        MyApp::ViewModels::MainViewModel ViewModel();

        // 状态文本(计算属性)
        hstring StatusText();

    private:
        MyApp::ViewModels::MainViewModel m_viewModel{ nullptr };
        winrt::event_token m_propertyChangedToken{};

        // 事件处理
        void OnViewModelPropertyChanged(
            winrt::IInspectable const& sender,
            winrt::PropertyChangedEventArgs const& args);
    };
}

MainWindow.xaml.cpp 实现文件:

cpp
#include "pch.h"
#include "MainWindow.xaml.h"
#include "MainWindow.g.cpp"

namespace winrt::MyApp::implementation
{
    MainWindow::MainWindow()
    {
        InitializeComponent();
        
        // 创建 ViewModel 实例
        m_viewModel = winrt::make<ViewModels::implementation::MainViewModel>();
        
        // 订阅 ViewModel 属性变化事件
        m_propertyChangedToken = m_viewModel.PropertyChanged(
            { this, &MainWindow::OnViewModelPropertyChanged }
        );
    }

    MyApp::ViewModels::MainViewModel MainWindow::ViewModel()
    {
        return m_viewModel;
    }

    hstring MainWindow::StatusText()
    {
        if (!m_viewModel)
            return L"正在初始化...";

        auto itemCount = m_viewModel.Items().Size();
        auto selectedItem = m_viewModel.SelectedItem();

        if (selectedItem.empty())
        {
            return L"共 " + winrt::to_hstring(itemCount) + L" 项,未选中任何项目";
        }
        else
        {
            return L"共 " + winrt::to_hstring(itemCount) + L" 项,已选中:" + selectedItem;
        }
    }

    void MainWindow::OnViewModelPropertyChanged(
        winrt::IInspectable const&,
        winrt::PropertyChangedEventArgs const& args)
    {
        auto propertyName = args.PropertyName();
        
        // 当影响状态文本的属性变化时,触发状态文本更新
        if (propertyName == L"SelectedItem")
        {
            // 通知 UI 更新状态文本
            if (auto bindingSource = this->try_as<winrt::INotifyPropertyChanged>())
            {
                // 如果 MainWindow 也实现了 INotifyPropertyChanged
                // 这里可以触发 StatusText 属性的更新通知
            }
        }
    }
}

这是第一部分内容,涵盖了 MVVM 架构基础、BaseViewModel 设计、命令模式实现和基本的 View-ViewModel 绑定。接下来的部分将包括:

  • 第二部分:高级绑定技巧和转换器
  • 第三部分:异步操作和错误处理
  • 第四部分:性能优化和最佳实践