MVVMLight项目的绑定及各种使用场景示例分析

目录
  • 一、绑定:
    • 1、元素绑定:
    • 2、非元素类型绑定:
      • 2.1 Source属性:
      • 2.2 RelativeSource 属性:
      • 2.3 DataContext 属性:
  • 二、绑定的各种使用场景:
    • 1、下拉框:
    • 2、单选框
    • 3、组合单选框
    • 4、复选框,复选框与单选框的使用情况类似:
    • 5、树形控件
    • 6、ListBox
    • 7、用户控件的集合绑定:

一、绑定:

主要包含元素绑定和非元素绑定两种。

1、元素绑定:

是绑定的最简单形式,源对象是WPF的元素,并且源对象的属性是依赖项属性。

根据我们之前的知识 ,依赖项属性具有内置的更改通知支持。所以当我们的源对象中改变依赖项属性的值时,会立即更新目标对象中的绑定属性。

以上篇的例子来重写,我们不用额外定义全局公开的属性来支持数据的显示。

如下:

  <StackPanel Orientation="Vertical" HorizontalAlignment="Left" >
         <TextBox x:Name="WelcomeText" Width="200" Margin="10,10,0,0"></TextBox>
         <TextBlock Text="{Binding ElementName=WelcomeText,Path=Text,StringFormat='Hello \{0\}'}" Margin="10,10,0,0"></TextBlock>
  </StackPanel>

TextBlock 绑定了名称为WelcomeText的元素,并且将Path指向Text属性,所以他的值会跟着 WelcomeText的变化而变化。

2、非元素类型绑定:

2.1 Source属性:

绑定具体的数据对象:如系统信息跟我们定义的资源数据。

定义Window下的全局资源

     <Window.Resources>
         <SolidColorBrush x:Key="BorderBrush">Red</SolidColorBrush>
     </Window.Resources>

应用到视图中

<StackPanel Margin="10,50,0,0" Orientation="Vertical" >
        <TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily},Path=Source}" ></TextBlock>
        <TextBlock Text="{Binding Source={StaticResource BorderBrush}}" Foreground="{Binding Source={StaticResource BorderBrush}}"  ></TextBlock>
</StackPanel>

结果:

2.2 RelativeSource 属性:

设置该属性 可以根据当前目标对象的相对关系指向源目标。比如获取当前对象的父亲对象、兄弟对象或者自身的其他属性等一些数据。

<StackPanel Margin="10,50,0,0" Orientation="Vertical" ToolTip="top" >
      <StackPanel Orientation="Horizontal" >
          <TextBlock Width="150" Text="获取自身宽度:"  ></TextBlock>
          <TextBlock Width="200" Text="{Binding Path=Width,RelativeSource={RelativeSource Mode=Self}}" ></TextBlock>
      </StackPanel>
      <StackPanel Orientation="Horizontal" ToolTip="parent" >
          <TextBlock Width="150" Text="查找上一层ToolTip:" ></TextBlock>
          <TextBlock Text="{Binding Path=ToolTip,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type StackPanel}}}"></TextBlock>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
          <TextBlock Width="150" Text="查找上二层ToolTip:" ></TextBlock>
          <TextBlock Text="{Binding Path=ToolTip,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type StackPanel},AncestorLevel=2}}"></TextBlock>
      </StackPanel>
</StackPan>

代码很容易理解,这边在创建RelativeSource的时候,mode模式有四种类型:

  Mode成员名称 说明
  FindAncestor
引用数据绑定元素的父链中的上级。 这可用于绑定到特定类型的上级或其子类。 若要指定 AncestorType 和/或 AncestorLevel,这就是应使用的模式。

  PreviousData
允许在当前显示的数据项列表中绑定上一个数据项(不是包含数据项的控件)。

  Self
引用正在其上设置绑定的元素,并允许你将该元素的一个属性绑定到同一元素的其他属性上。

  TemplatedParent
引用应用了模板的元素,其中此模板中存在数据绑定元素。 这类似于设置 TemplateBindingExtension,且仅在 Binding 位于模板内部时适用。

注意:AncestorType 指得是查找的对象类型,AncestorLevel 代表搜索的层级的位置,如果是3,则忽略前两个发现的元素。

结果:

2.3 DataContext 属性:

如果想将一个对象绑定到一个由多个元素组成的视图块或者复合元素中,用DataContext 会更好开发和维护。如下

<StackPanel Orientation="Vertical" DataContext="UInfo" >
    <StackPanel Orientation="Horizontal" >
        <TextBlock Text="名称:" Width="100" ></TextBlock>
        <TextBox Text="{Binding Name}" Width="100" ></TextBox>
    </StackPanel>
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="性别:" Width="100" ></TextBlock>
        <TextBox Text="{Binding Sex}" Width="100" ></TextBox>
    </StackPanel>
</StackPanel>

二、绑定的各种使用场景:

数据绑定有普通的控件绑定应用:比如 下拉框、单选框、复选框、普通文本框 、日期框等;

复杂的绑定有数据列表绑定,用户控件信息绑定等,比如 ListBox,DataGrid,UserControl绑定等。

1、下拉框:

View代码:

<StackPanel Margin="10,20,0,50">
  <TextBlock Text="下拉框" FontWeight="Bold" FontSize="12" Margin="0,5,0,5" ></TextBlock>
  <DockPanel x:Name="Combbox" >
      <StackPanel DockPanel.Dock="Left" Width="240">
          <ComboBox Width="200" HorizontalAlignment="Left" ItemsSource="{Binding CombboxList}" SelectedItem="{Binding CombboxItem}" DisplayMemberPath="Text" SelectedValuePath="Key" ></ComboBox>
      </StackPanel>

      <StackPanel DockPanel.Dock="Right" Width="240" Orientation="Horizontal" DataContext="{Binding CombboxItem}" >
          <TextBlock Text="{Binding Key,StringFormat='结果:\{0\}'}" Margin="0,0,15,0" ></TextBlock>
          <TextBlock Text="{Binding Text}"></TextBlock>
      </StackPanel>

  </DockPanel>
</StackPanel>

Model代码:

 public class ComplexInfoModel:ObservableObject
    {
        private String key;
        /// <summary>
        /// Key值
        /// </summary>
        public String Key
        {
            get { return key; }
            set { key = value; RaisePropertyChanged(()=>Key); }
        }
        private String text;
        /// <summary>
        /// Text值
        /// </summary>
        public String Text
        {
            get { return text; }
            set { text = value; RaisePropertyChanged(()=>Text); }
        }
    }

ViewModel代码:

        #region 下拉框相关
         private ComplexInfoModel combboxItem;
         /// <summary>
         /// 下拉框选中信息
         /// </summary>
         public ComplexInfoModel CombboxItem
         {
             get { return combboxItem; }
             set { combboxItem = value; RaisePropertyChanged(() => CombboxItem); }
         }

         private List<ComplexInfoModel> combboxList;
         /// <summary>
         /// 下拉框列表
         /// </summary>
         public List<ComplexInfoModel> CombboxList
         {
             get { return combboxList; }
             set { combboxList = value; RaisePropertyChanged(()=>CombboxList); }
         }
         #endregio

说明:CombboxItem是一个全局的属性,作用在当前页面的数据上下文中,结果显示的内容指向下拉框中的选中值,达到共用一个数据的目的。

这边有四个地方需要注意的:ItemsSource:数据源;SelectedItem:选中的项;DisplayMemberPath:绑定时显示的所属值;SelectedValuePath :绑定时候key的所属值。

结果:

2、单选框

    <StackPanel Margin="10,0,0,50">
         <TextBlock Text="单选框" FontWeight="Bold" FontSize="12" Margin="0,5,0,5" ></TextBlock>
         <DockPanel x:Name="RadioButton" >
             <StackPanel DockPanel.Dock="Left" Width="240">
                 <RadioButton Content="{Binding SingleRadio}" IsChecked="{Binding IsSingleRadioCheck}" HorizontalAlignment="Right" Width="240" >
                 </RadioButton>
             </StackPanel>
             <StackPanel DockPanel.Dock="Right" Width="240" Orientation="Horizontal">
                 <TextBlock Text="{Binding IsSingleRadioCheck,StringFormat='结果:\{0\}'}" ></TextBlock>
             </StackPanel>
         </DockPanel>
     </StackPanel>

说明:注意这边使用到了两个属性: SingleRadio,IsSingleRadioCheck,一个用于显示单选框内容,一个用于表示是否选中

结果:

3、组合单选框

我们一般会用单选框做组合表示唯一选项,比如性别包含男女,但是只能选择一个。而更多的场景是包含多个选项,但是只能单选的,这时候就需要做单选框组。

  <StackPanel Margin="10,0,0,50">
       <TextBlock Text="组合单选框" FontWeight="Bold" FontSize="12" Margin="0,5,0,5"></TextBlock>
       <DockPanel x:Name="GroupRadioButton" >
           <StackPanel DockPanel.Dock="Left" Width="240">
               <ItemsControl ItemsSource="{Binding RadioButtons}">
                   <ItemsControl.ItemTemplate>
                       <DataTemplate>
                           <RadioButton Content="{Binding Content}" IsChecked="{Binding IsCheck}" GroupName="RadioButtons"
                                        Command="{Binding DataContext.RadioCheckCommand,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ItemsControl}}">
                           </RadioButton>
                       </DataTemplate>
                   </ItemsControl.ItemTemplate>
               </ItemsControl>
           </StackPanel>

           <StackPanel DockPanel.Dock="Right" Width="240" Orientation="Horizontal">
               <TextBlock Text="{Binding RadioButton.Content,StringFormat='结果:\{0\}'}" ></TextBlock>
           </StackPanel>
       </DockPanel>
  </StackPanel

这边使用了ItemsControl,可以根据模板来定义内容,我们在模板中放置我们需要用到的内容。这边需要注意的是:GroupName用一样的,来代表这是一个单选控件组合。

这边有是三个属性进行绑定相关:

RadioButtons:单选框列表数据(循环绑定);Content:单选框显示的内容;IsCheck:单选框的是否选中。

结果:

4、复选框,复选框与单选框的使用情况类似:

<StackPanel Margin="10,0,0,50">
            <TextBlock Text="复合框" FontWeight="Bold" FontSize="12" Margin="0,5,0,5" ></TextBlock>
            <DockPanel x:Name="GroupCheckButton" >
                <StackPanel DockPanel.Dock="Left" Width="240">
                    <ItemsControl ItemsSource="{Binding CheckButtons}" x:Name="cbt" >
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <CheckBox Content="{Binding Content}" IsChecked="{Binding IsCheck}"
                                             Command="{Binding DataContext.CheckCommand,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ItemsControl}}"/>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </StackPanel>
                <StackPanel DockPanel.Dock="Right" Width="240" Orientation="Horizontal">
                    <TextBlock Text="{Binding CheckInfo,StringFormat='结果:\{0\}'}" ></TextBlock>
                </StackPanel>
            </DockPanel>
</StackPanel>

结果:

5、树形控件

View代码:

<StackPanel Margin="10,0,0,50">
            <TextBlock Text="树" FontWeight="Bold" FontSize="12" Margin="0,5,0,5" ></TextBlock>
            <DockPanel x:Name="TreeButton" >
                <StackPanel DockPanel.Dock="Left" Width="240">
                        <TreeView ItemsSource="{Binding TreeInfo}" x:Name="tree" BorderThickness="0">
                        <TreeView.ItemTemplate>
                            <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                                <TextBlock Text="{Binding NodeName}"/>
                            </HierarchicalDataTemplate>
                        </TreeView.ItemTemplate>
                    </TreeView>
                </StackPanel>

                <StackPanel DockPanel.Dock="Right" Width="240" Orientation="Horizontal" DataContext="{Binding SelectedItem,ElementName=tree}">
                        <TextBlock Text="结果:"/>
                    <TextBlock Text="{Binding NodeID,StringFormat='NodeID:\{0\}'}"  Margin="0,0,20,0"  />
                    <TextBlock Text="{Binding NodeName,StringFormat='NodeName:\{0\}'}"/>
                </StackPanel>
            </DockPanel>
</StackPanel>

Model代码

  public class TreeNodeModel : ObservableObject
     {
         public string NodeID { get; set; }
         public string NodeName { get; set; }
         public List<TreeNodeModel> Children { get; set; }
     }

ViewModel代码

 #region 树控件
        private List<TreeNodeModel> treeInfo;
        /// <summary>
        /// 树控件数据信息
        /// </summary>
        public List<TreeNodeModel> TreeInfo
        {
            get { return treeInfo; }
            set { treeInfo = value; RaisePropertyChanged(()=>TreeInfo); }
        }
  #endregion

结果:

6、ListBox

当我们需要用到循环的列表内容,并且模板化程度高的时候,建议使用ListBox来做绑定。

View代码:

<StackPanel Margin="10,0,0,50" Orientation="Vertical" >
  <TextBlock Text="ListBox模板" FontWeight="Bold" FontSize="12" Margin="0,5,0,5" ></TextBlock>
      <DockPanel >
          <StackPanel HorizontalAlignment="Left" DockPanel.Dock="Left" >
              <ListBox x:Name="lb" ItemsSource="{Binding ListBoxData}" Width="500" BorderThickness="0" >
                  <ListBox.ItemsPanel>
                      <ItemsPanelTemplate>
                          <WrapPanel Width="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"/>
                      </ItemsPanelTemplate>
                  </ListBox.ItemsPanel>
                  <ListBox.ItemTemplate>
                      <DataTemplate>
                          <StackPanel>
                              <Image Source="{Binding Img}" Width="96" Height="96"/>
                              <TextBlock HorizontalAlignment="Center" Text="{Binding Info}"/>
                          </StackPanel>
                      </DataTemplate>
                  </ListBox.ItemTemplate>
              </ListBox>
          </StackPanel>
          <StackPanel DockPanel.Dock="Right" DataContext="{Binding SelectedItem,ElementName=lb}" Margin="15" Orientation="Vertical" >
              <TextBlock Text="{Binding Info,StringFormat='选中:\{0\}'}" ></TextBlock>
          </StackPanel>
      </DockPanel>
</StackPanel>

ViewModel代码:

     #region ListBox 模板
      private IEnumerable listBoxData;
      /// <summary>
      /// LisBox数据模板
      /// </summary>
      public IEnumerable ListBoxData
      {
          get { return listBoxData; }
          set { listBoxData = value;RaisePropertyChanged(()=>ListBoxData); }
      }
      #endregion

初始数据:

private void InitListBoxList()
     {
      ListBoxData = new ObservableCollection<dynamic>(){
        new { Img="/MVVMLightDemo;component/Images/1.jpg",Info="樱桃" },
        new { Img="/MVVMLightDemo;component/Images/2.jpg",Info="葡萄" },
        new { Img="/MVVMLightDemo;component/Images/3.jpg",Info="苹果" },
        new { Img="/MVVMLightDemo;component/Images/4.jpg",Info="猕猴桃" },
        new { Img="/MVVMLightDemo;component/Images/5.jpg",Info="柠檬" },
     };
 

结果:

7、用户控件的集合绑定:

ListBox的列表绑定远远不能满足我们实际工作中的需求,

出于对灵活性、复用性以及代码精简的考虑,需要保证循环列表中的单个元素是独立的元素片段,类似Web中的局部视图。 这时候,使用用户控件会好很多。

我们先写一个用户控件,分别设置了他的样式和绑定的属性值,如下:

<UserControl x:Class="MVVMLightDemo.Content.FruitInfoView"
              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="300" d:DesignWidth="300">
     <Grid>
         <Grid.Resources>
             <Style TargetType="{x:Type StackPanel}">
                 <Style.Triggers>
                     <Trigger Property="IsMouseOver" Value="True">
                         <Setter Property="RenderTransform">
                             <Setter.Value>
                                 <RotateTransform Angle="10"></RotateTransform>
                             </Setter.Value>
                         </Setter>
                         <Setter Property="Background" Value="#3B9CFB" />
                     </Trigger>
                 </Style.Triggers>
             </Style>
         </Grid.Resources>

         <StackPanel Orientation="Vertical" Margin="10">
             <Image Source="{Binding Img}" Width="96" Height="96" />
             <TextBlock HorizontalAlignment="Center" Text="{Binding Info}"/>
         </StackPanel>

     </Grid>
</UserControl>

在目标视图页面注册并使用:

xmlns:Content="clr-namespace:MVVMLightDemo.Content"
<StackPanel Margin="10,0,0,50" Orientation="Vertical" >
                    <TextBlock Text="用户控件模板列表" FontWeight="Bold" FontSize="12" Margin="0,5,0,5" ></TextBlock>
                    <StackPanel HorizontalAlignment="Left" Width="500" >
                         <ItemsControl ItemsSource="{Binding FiList}" HorizontalAlignment="Left" >
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <Content:FruitInfoView />
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                            <!-- 面板显示模板 -->
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <WrapPanel Orientation="Horizontal">
                                    </WrapPanel>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                        </ItemsControl>
                    </StackPanel>
</StackPanel>

结果:

后记:这篇更确切的说是绑定的相关知识,只是应用了MVVM模式来实现。

工作太忙了,写的太慢,其实后面几篇都已经成稿了,一直放在Note里面等待认真检查,品质太差怕误导其他开发人员。

以上就是MVVMLight项目的绑定及各种使用场景示例分析的详细内容,更多关于MVVMLight项目的绑定及各种使用场景的资料请关注我们其它相关文章!

(0)

相关推荐

  • MVVMLight项目的绑定及各种使用场景示例分析

    目录 一.绑定: 1.元素绑定: 2.非元素类型绑定: 2.1 Source属性: 2.2 RelativeSource 属性: 2.3 DataContext 属性: 二.绑定的各种使用场景: 1.下拉框: 2.单选框 3.组合单选框 4.复选框,复选框与单选框的使用情况类似: 5.树形控件 6.ListBox 7.用户控件的集合绑定: 一.绑定: 主要包含元素绑定和非元素绑定两种. 1.元素绑定: 是绑定的最简单形式,源对象是WPF的元素,并且源对象的属性是依赖项属性. 根据我们之前的知识

  • MVVMLight项目之绑定在表单验证上的应用示例分析

    目录 常见的表单验证机制有如下几种: 验证交互的关系模式如图: 下面详细描述下这三种验证模式 1.Exception 验证: 2.ValidationRule 验证: 3.IDataErrorInfo 验证: 表单验证是MVVM体系中的重要一块.而绑定除了推动 Model-View-ViewModel (MVVM) 模式松散耦合 逻辑.数据 和 UI定义 的关系之外,还为业务数据验证方案提供强大而灵活的支持. WPF 中的数据绑定机制包括多个选项,可用于在创建可编辑视图时校验输入数据的有效性.

  • MVVMLight项目之双向数据绑定

    目录 第一步:先写一个Model,里面包含我们需要的数据信息 第二步:写一个ViewModel 第三步:在ViewModelLocator中注册我们写好的ViewModel: 第四步:编写View(注意标红的代码) MVVM和MVVMLight框架介绍及在项目中的使用详解 MVVMLight项目Model View结构及全局视图模型注入器 前篇我们已经了解了MVVM的框架结构和运行原理.这里我们来看一下伟大的双向数据绑定. 说到双向绑定,大家比较熟悉的应该就是AngularJS了,几乎所有的An

  • android studio项目:绑定服务和线程实现计时器

    实验目的: 熟悉和掌握Android线程的使用 实验要求: 1.完成一个秒表,具备启停功能 2.通过绑定服务实现功能,通过Thread+handler更新界面 这章节没花什么时间去学,其他事情又很多,所以只是简单实现了一下,在生命周期那里还是有些没处理的地方,因此 主要思路是:在服务中启动一个线程实现计数的功能,并且每隔10ms调用一下更新界面的函数,这需要用到Thread+handler,当然还需要一些控制启停的公有函数供activity调用,同过绑定的服务的方式,activity中可以获得服

  • android studio项目:绑定服务和线程实现计时器

    实验目的: 熟悉和掌握Android线程的使用 实验要求: 1.完成一个秒表,具备启停功能 2.通过绑定服务实现功能,通过Thread+handler更新界面 这章节没花什么时间去学,其他事情又很多,所以只是简单实现了一下,在生命周期那里还是有些没处理的地方,因此 主要思路是:在服务中启动一个线程实现计数的功能,并且每隔10ms调用一下更新界面的函数,这需要用到Thread+handler,当然还需要一些控制启停的公有函数供activity调用,同过绑定的服务的方式,activity中可以获得服

  • ES2020 已定稿,真实场景案例分析

    近年来,JavaScript 的发展非常迅速. 尤其是在2015 年 ES6 发布之后,情况变得更好. 现在 许多新的特性被提议包括在 ES2020版本中.好消息是这些已经已经敲定. 现在,我们获得了最终定稿的功能清单,它们将在被批准发布之后出现在备受期待的 ES2020 中. 其中一些功能使我非常兴奋,因为在它们存在之前编写代码时遇到将会遇到很多麻烦. 让我们看看它们是什么吧! 可选链操作符(Optional Chaining Operator) 对我个人来说,这是 ES2020最令人兴奋的特

  • java中ThreadLocal的应用场景实例分析

    说到线程的安全,我们可以通过ThreadLocal来解决.但作为一种强大的变量,它的应用场景远不止如此.在各类的框架中,我们依然可以使用来对它们进行管理.同时在使用ThreadLocal时需要注意内存泄漏的问题.下面我们就这两点进行分析,并带来对应代码的展示. 1.各种框架中的应用 Spring框架的事务管理中使用ThreadLocal来管理连接,每个线程是单独的连接,当事务失败时不能影响到其他线程的事务过程或结果,还有大家耳闻目睹的ORM框架.Mybatis也是用ThreadLocal管理,S

  • 在SpringBoot项目中的使用Swagger的方法示例

    一. 首先Swagger是什么? Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视化 RESTful 风格的 Web 服务.总体目标是使客户端和文件系统作为服务器以同样的速度来更新.文件的方法,参数和模型紧密集成到服务器端的代码,允许API来始终保持同步.Swagger官方API文档:https://swagger.io/ 作用:   1. 接口的文档在线自动生成.   2. 功能测试. Swagger的主见介绍:    Swagger Codegen: 通过Codegen 可

  • webservice实现springboot项目间接口调用与对象传递示例

    目录 一.百度百科 二.webservice的技术支持 1.XML和XSD 2.SOAP 3.WSDL 4.UDDI 5.调用RPC与消息传递 三.webservice的应用场景和弊端 1.webservice的应用场景 2.webservice的弊端 四.webservice代码实例 服务端项目代码 客户端项目代码: 一.百度百科 Web Service是一个平台独立的,低耦合的,自包含的.基于可编程的web的应用程序,可使用开放的XML(标准通用标记语言下的一个子集)标准来描述.发布.发现.

  • JS前端中的设计模式和使用场景示例详解

    目录 引言 策略模式 1.绩效考核 2.表单验证 策略模式的优缺点: 代理模式 1.图片懒加载: 2.缓存代理 总结 引言 相信大家在日常学习和工作中都多多少少听说/了解/使用过 设计模式,我们都知道,使用恰当的设计模式可以优化我们的代码,那你是否知道对于前端开发哪些 设计模式 是日常工作经常用到或者必须掌握的呢?本文我将带大家一起学习下前端常见的设计模式以及它们的 使用场景!!! 本文主讲: 策略模式 代理模式 适合人群: 前端人员 设计模式小白/想知道如何在项目中使用设计模式 策略模式 策略

随机推荐