zoukankan      html  css  js  c++  java
  • WPF自定义控件与样式(14)-轻量MVVM模式实践

    一.前言

      申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接。

      MVVM是WPF中一个非常实用的编程模式,充分利用了WPF的绑定机制,体现了WPF数据驱动的优势。

     图片来源:(WPF的MVVM

      关于MVVM网上很多介绍或者示例,本文不多做介绍了,本文的主要目的是提供一个轻量级的View Model实现,本文的主要内容:

    • 依赖通知InotifyPropertyChanged实现;
    • 命令Icommand的实现;
    • 消息的实现;
    • 一个简单MVVM示例;

      对于是否要使用MVVM、如何使用,个人觉得根据具体需求可以灵活处理,不用纠结于模式本身。用了MVVM,后置*.cs文件就不一定不允许写任何代码,混合着用也是没有问题的, 只要自己决的方便、代码结构清晰、维护方便即可。

    二.依赖通知InotifyPropertyChanged实现

      依赖通知InotifyPropertyChanged是很简单的一个接口,是View Model标配的接口,一个典型的实现(BaseNotifyPropertyChanged):  

       /// <summary>
        /// 实现了属性更改通知的基类
        /// </summary>
        public class BaseNotifyPropertyChanged : System.ComponentModel.INotifyPropertyChanged
        {
            /// <summary>
            /// 属性值变化时发生
            /// </summary>
            /// <param name="propertyName"></param>
            protected virtual void OnPropertyChanged(string propertyName)
            {
                if (this.PropertyChanged != null)
                    this.PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
            }
    
            public virtual event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
        }

      然后使用方式就是这样的:  

            public int _Age;
    
            public int Age
            {
                get { return this._Age; }
                set { this._Age = value; base.OnPropertyChanged("Age"); }
            }

      上面的代码有硬编码,有代码洁癖的人就不爽了,因此网上有多种解决方式,比如这篇:WPF MVVM之INotifyPropertyChanged接口的几种实现方式。本文的实现方式如下,使用表达式树:

            /// <summary>
            /// 属性值变化时发生
            /// </summary>
            /// <param name="propertyName"></param>
            protected virtual void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
            {
                var propertyName = (propertyExpression.Body as MemberExpression).Member.Name;
                this.OnPropertyChanged(propertyName);
            }

      使用上避免了硬编码,使用示例:  

            public string _Name;
            public string Name
            {
                get { return this._Name; }
                set { this._Name = value; base.OnPropertyChanged(() => this.Name); }
            }

    三.命令Icommand的实现

      命令的实现也很简单,实现Icommand的几个接口就OK了, 考虑到使用时能更加方便,无参数RelayCommand实现:  

        /// <summary>
        /// 广播命令:基本ICommand实现接口
        /// </summary>
        public class RelayCommand : ICommand
        {
            public Action ExecuteCommand { get; private set; }
            public Func<bool> CanExecuteCommand { get; private set; }
    
            public RelayCommand(Action executeCommand, Func<bool> canExecuteCommand)
            {
                this.ExecuteCommand = executeCommand;
                this.CanExecuteCommand = canExecuteCommand;
            }
    
            public RelayCommand(Action executeCommand)
                : this(executeCommand, null) { }
    
            /// <summary>
            /// 定义在调用此命令时调用的方法。
            /// </summary>
            /// <param name="parameter">此命令使用的数据。如果此命令不需要传递数据,则该对象可以设置为 null。</param>
            public void Execute(object parameter)
            {
                if (this.ExecuteCommand != null) this.ExecuteCommand();
            }
    
            /// <summary>
            /// 定义用于确定此命令是否可以在其当前状态下执行的方法。
            /// </summary>
            /// <returns>
            /// 如果可以执行此命令,则为 true;否则为 false。
            /// </returns>
            /// <param name="parameter">此命令使用的数据。如果此命令不需要传递数据,则该对象可以设置为 null。</param>
            public bool CanExecute(object parameter)
            {
                return CanExecuteCommand == null || CanExecuteCommand();
            }
    
            public event EventHandler CanExecuteChanged
            {
                add { if (this.CanExecuteCommand != null) CommandManager.RequerySuggested += value; }
                remove { if (this.CanExecuteCommand != null) CommandManager.RequerySuggested -= value; }
            }
        }

      泛型参数RelayCommand<T>的版本:  

        /// <summary>
        /// 广播命令:基本ICommand实现接口,带参数
        /// </summary>
        public class RelayCommand<T> : ICommand
        {
            public Action<T> ExecuteCommand { get; private set; }
    
            public Predicate<T> CanExecuteCommand { get; private set; }
    
            public RelayCommand(Action<T> executeCommand, Predicate<T> canExecuteCommand)
            {
                this.ExecuteCommand = executeCommand;
                this.CanExecuteCommand = canExecuteCommand;
            }
    
            public RelayCommand(Action<T> executeCommand)
                : this(executeCommand, null) { }
    
            /// <summary>
            /// 定义在调用此命令时调用的方法。
            /// </summary>
            /// <param name="parameter">此命令使用的数据。如果此命令不需要传递数据,则该对象可以设置为 null。</param>
            public void Execute(object parameter)
            {
                if (this.ExecuteCommand != null) this.ExecuteCommand((T)parameter);
            }
    
            /// <summary>
            /// 定义用于确定此命令是否可以在其当前状态下执行的方法。
            /// </summary>
            /// <returns>
            /// 如果可以执行此命令,则为 true;否则为 false。
            /// </returns>
            /// <param name="parameter">此命令使用的数据。如果此命令不需要传递数据,则该对象可以设置为 null。</param>
            public bool CanExecute(object parameter)
            {
                return CanExecuteCommand == null || CanExecuteCommand((T)parameter);
            }
    
            public event EventHandler CanExecuteChanged
            {
                add { if (this.CanExecuteCommand != null) CommandManager.RequerySuggested += value; }
                remove { if (this.CanExecuteCommand != null) CommandManager.RequerySuggested -= value; }
            }
        }

      带参数和不带参数的命令XAML绑定方式:  

    <core:FButton Margin="5 0 0 0" Command="{Binding ShowUserCommand}">ShowUser</core:FButton>
    <core:FButton Margin="5 0 0 0" Command="{Binding SetNameCommand}" FIcon="&#xe60c;"
                              CommandParameter="{Binding Text,ElementName=txtSetName}">SetName</core:FButton>

      上面是针对提供Command模式的控件示例, 但对于其他事件呢,比如MouseOver如何绑定呢?可以借用System.Windows.Interactivity.dll,其中的 Interaction 可以帮助我们实现对命令的绑定,这是在微软Blend中提供的。添加dll应用,然后添加命名空间:

      xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

                <TextBlock VerticalAlignment="Center" Margin="5 0 0 0" Text="MoseOver" x:Name="txbMessage">
                    <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseMove">
                        <i:InvokeCommandAction Command="{Binding MouseOverCommand}" CommandParameter="{Binding ElementName=txbMessage}"></i:InvokeCommandAction>
                    </i:EventTrigger>
                    </i:Interaction.Triggers>
                </TextBlock>

    四.消息的实现

      消息类Messenger主要目的是实现View与View Model及各个模块之间的通信。本文的消息类Messenger,参考自网络开源的实现(MVVMFoundation)。实现了松散耦合的消息通知机制,对于消息传输参数,内部使用了弱引用(WeakReference),以防止内存泄漏代码:  

        /// <summary>
        /// Provides loosely-coupled messaging between various colleague objects.  All references to objects are stored weakly, to prevent memory leaks.
        /// 提供松散耦合的消息通知机制,为防止内存泄漏,所有对象都使用了弱引用(WeakReference)
        /// </summary>
        public class Messenger
        {
            #region Constructor
    
            public Messenger()
            {
            }
    
            #endregion // Constructor
    
            #region Register
    
            /// <summary>
            /// Registers a callback method, with no parameter, to be invoked when a specific message is broadcasted.
            /// 注册消息监听
            /// </summary>
            /// <param name="message">The message to register for.</param>
            /// <param name="callback">The callback to be called when this message is broadcasted.</param>
            public void Register(string message, Action callback)
            {
                this.Register(message, callback, null);
            }
    
            /// <summary>
            /// Registers a callback method, with a parameter, to be invoked when a specific message is broadcasted.
            /// 注册消息监听
            /// </summary>
            /// <param name="message">The message to register for.</param>
            /// <param name="callback">The callback to be called when this message is broadcasted.</param>
            public void Register<T>(string message, Action<T> callback)
            {
                this.Register(message, callback, typeof(T));
            }
    
            void Register(string message, Delegate callback, Type parameterType)
            {
                if (String.IsNullOrEmpty(message))
                    throw new ArgumentException("'message' cannot be null or empty.");
    
                if (callback == null)
                    throw new ArgumentNullException("callback");
    
                this.VerifyParameterType(message, parameterType);
    
                _messageToActionsMap.AddAction(message, callback.Target, callback.Method, parameterType);
            }
    
            [Conditional("DEBUG")]
            void VerifyParameterType(string message, Type parameterType)
            {
                Type previouslyRegisteredParameterType = null;
                if (_messageToActionsMap.TryGetParameterType(message, out previouslyRegisteredParameterType))
                {
                    if (previouslyRegisteredParameterType != null && parameterType != null)
                    {
                        if (!previouslyRegisteredParameterType.Equals(parameterType))
                            throw new InvalidOperationException(string.Format(
                                "The registered action's parameter type is inconsistent with the previously registered actions for message '{0}'.
    Expected: {1}
    Adding: {2}",
                                message,
                                previouslyRegisteredParameterType.FullName,
                                parameterType.FullName));
                    }
                    else
                    {
                        // One, or both, of previouslyRegisteredParameterType or callbackParameterType are null.
                        if (previouslyRegisteredParameterType != parameterType)   // not both null?
                        {
                            throw new TargetParameterCountException(string.Format(
                                "The registered action has a number of parameters inconsistent with the previously registered actions for message "{0}".
    Expected: {1}
    Adding: {2}",
                                message,
                                previouslyRegisteredParameterType == null ? 0 : 1,
                                parameterType == null ? 0 : 1));
                        }
                    }
                }
            }
    
            #endregion // Register
    
            #region Notify
    
            /// <summary>
            /// Notifies all registered parties that a message is being broadcasted.
            /// 发送消息通知,触发监听执行
            /// </summary>
            /// <param name="message">The message to broadcast.</param>
            /// <param name="parameter">The parameter to pass together with the message.</param>
            public void Notify(string message, object parameter)
            {
                if (String.IsNullOrEmpty(message))
                    throw new ArgumentException("'message' cannot be null or empty.");
    
                Type registeredParameterType;
                if (_messageToActionsMap.TryGetParameterType(message, out registeredParameterType))
                {
                    if (registeredParameterType == null)
                        throw new TargetParameterCountException(string.Format("Cannot pass a parameter with message '{0}'. Registered action(s) expect no parameter.", message));
                }
    
                var actions = _messageToActionsMap.GetActions(message);
                if (actions != null)
                    actions.ForEach(action => action.DynamicInvoke(parameter));
            }
    
            /// <summary>
            /// Notifies all registered parties that a message is being broadcasted.
            /// 发送消息通知,触发监听执行
            /// </summary>
            /// <param name="message">The message to broadcast.</param>
            public void Notify(string message)
            {
                if (String.IsNullOrEmpty(message))
                    throw new ArgumentException("'message' cannot be null or empty.");
    
                Type registeredParameterType;
                if (_messageToActionsMap.TryGetParameterType(message, out registeredParameterType))
                {
                    if (registeredParameterType != null)
                        throw new TargetParameterCountException(string.Format("Must pass a parameter of type {0} with this message. Registered action(s) expect it.", registeredParameterType.FullName));
                }
    
                var actions = _messageToActionsMap.GetActions(message);
                if (actions != null)
                    actions.ForEach(action => action.DynamicInvoke());
            }
    
            #endregion // NotifyColleauges
    
            #region MessageToActionsMap [nested class]
    
            /// <summary>
            /// This class is an implementation detail of the Messenger class.
            /// </summary>
            private class MessageToActionsMap
            {
                #region Constructor
    
                internal MessageToActionsMap()
                {
                }
    
                #endregion // Constructor
    
                #region AddAction
    
                /// <summary>
                /// Adds an action to the list.
                /// </summary>
                /// <param name="message">The message to register.</param>
                /// <param name="target">The target object to invoke, or null.</param>
                /// <param name="method">The method to invoke.</param>
                /// <param name="actionType">The type of the Action delegate.</param>
                internal void AddAction(string message, object target, MethodInfo method, Type actionType)
                {
                    if (message == null)
                        throw new ArgumentNullException("message");
    
                    if (method == null)
                        throw new ArgumentNullException("method");
    
                    lock (_map)
                    {
                        if (!_map.ContainsKey(message))
                            _map[message] = new List<WeakAction>();
    
                        _map[message].Add(new WeakAction(target, method, actionType));
                    }
                }
    
                #endregion // AddAction
    
                #region GetActions
    
                /// <summary>
                /// Gets the list of actions to be invoked for the specified message
                /// </summary>
                /// <param name="message">The message to get the actions for</param>
                /// <returns>Returns a list of actions that are registered to the specified message</returns>
                internal List<Delegate> GetActions(string message)
                {
                    if (message == null)
                        throw new ArgumentNullException("message");
    
                    List<Delegate> actions;
                    lock (_map)
                    {
                        if (!_map.ContainsKey(message))
                            return null;
    
                        List<WeakAction> weakActions = _map[message];
                        actions = new List<Delegate>(weakActions.Count);
                        for (int i = weakActions.Count - 1; i > -1; --i)
                        {
                            WeakAction weakAction = weakActions[i];
                            if (weakAction == null)
                                continue;
    
                            Delegate action = weakAction.CreateAction();
                            if (action != null)
                            {
                                actions.Add(action);
                            }
                            else
                            {
                                // The target object is dead, so get rid of the weak action.
                                weakActions.Remove(weakAction);
                            }
                        }
    
                        // Delete the list from the map if it is now empty.
                        if (weakActions.Count == 0)
                            _map.Remove(message);
                    }
    
                    // Reverse the list to ensure the callbacks are invoked in the order they were registered.
                    actions.Reverse();
    
                    return actions;
                }
    
                #endregion // GetActions
    
                #region TryGetParameterType
    
                /// <summary>
                /// Get the parameter type of the actions registered for the specified message.
                /// </summary>
                /// <param name="message">The message to check for actions.</param>
                /// <param name="parameterType">
                /// When this method returns, contains the type for parameters 
                /// for the registered actions associated with the specified message, if any; otherwise, null.
                /// This will also be null if the registered actions have no parameters.
                /// This parameter is passed uninitialized.
                /// </param>
                /// <returns>true if any actions were registered for the message</returns>
                internal bool TryGetParameterType(string message, out Type parameterType)
                {
                    if (message == null)
                        throw new ArgumentNullException("message");
    
                    parameterType = null;
                    List<WeakAction> weakActions;
                    lock (_map)
                    {
                        if (!_map.TryGetValue(message, out weakActions) || weakActions.Count == 0)
                            return false;
                    }
                    parameterType = weakActions[0].ParameterType;
                    return true;
                }
    
                #endregion // TryGetParameterType
    
                #region Fields
    
                // Stores a hash where the key is the message and the value is the list of callbacks to invoke.
                readonly Dictionary<string, List<WeakAction>> _map = new Dictionary<string, List<WeakAction>>();
    
                #endregion // Fields
            }
    
            #endregion // MessageToActionsMap [nested class]
    
            #region WeakAction [nested class]
    
            /// <summary>
            /// This class is an implementation detail of the MessageToActionsMap class.
            /// </summary>
            private class WeakAction
            {
                #region Constructor
    
                /// <summary>
                /// Constructs a WeakAction.
                /// </summary>
                /// <param name="target">The object on which the target method is invoked, or null if the method is static.</param>
                /// <param name="method">The MethodInfo used to create the Action.</param>
                /// <param name="parameterType">The type of parameter to be passed to the action. Pass null if there is no parameter.</param>
                internal WeakAction(object target, MethodInfo method, Type parameterType)
                {
                    if (target == null)
                    {
                        _targetRef = null;
                    }
                    else
                    {
                        _targetRef = new WeakReference(target);
                    }
    
                    _method = method;
    
                    this.ParameterType = parameterType;
    
                    if (parameterType == null)
                    {
                        _delegateType = typeof(Action);
                    }
                    else
                    {
                        _delegateType = typeof(Action<>).MakeGenericType(parameterType);
                    }
                }
    
                #endregion // Constructor
    
                #region CreateAction
    
                /// <summary>
                /// Creates a "throw away" delegate to invoke the method on the target, or null if the target object is dead.
                /// </summary>
                internal Delegate CreateAction()
                {
                    // Rehydrate into a real Action object, so that the method can be invoked.
                    if (_targetRef == null)
                    {
                        return Delegate.CreateDelegate(_delegateType, _method);
                    }
                    else
                    {
                        try
                        {
                            object target = _targetRef.Target;
                            if (target != null)
                                return Delegate.CreateDelegate(_delegateType, target, _method);
                        }
                        catch
                        {
                        }
                    }
    
                    return null;
                }
    
                #endregion // CreateAction
    
                #region Fields
    
                internal readonly Type ParameterType;
    
                readonly Type _delegateType;
                readonly MethodInfo _method;
                readonly WeakReference _targetRef;
    
                #endregion // Fields
            }
    
            #endregion // WeakAction [nested class]
    
            #region Fields
    
            readonly MessageToActionsMap _messageToActionsMap = new MessageToActionsMap();
    
            #endregion // Fields
        }
    View Code

      在后面的示例中有简单使用。

    五.简单MVVM示例

    5.1 View Model定义实现

      实现一个UserViewModel,定义了两个通知属性,3个命令,用于在XAML中实现不同的命令绑定处理,还注册了一个消息,代码:  

       public class UserViewModel : BaseNotifyPropertyChanged
        {
            public string _Name;
            public string Name
            {
                get { return this._Name; }
                set { this._Name = value; base.OnPropertyChanged(() => this.Name); }
            }
    
            public int _Age;
    
            public int Age
            {
                get { return this._Age; }
                set { this._Age = value; base.OnPropertyChanged("Age"); }
            }
    
            public RelayCommand<string> SetNameCommand { get; private set; }
            public RelayCommand ShowUserCommand { get; private set; }
            public RelayCommand<FrameworkElement> MouseOverCommand { get; private set; }
    
            public UserViewModel()
            {
                this.SetNameCommand = new RelayCommand<string>(this.SetName);
                this.ShowUserCommand = new RelayCommand(this.ShowUser);
                this.MouseOverCommand = new RelayCommand<FrameworkElement>(this.MouseOver);
                Page_MVVM.GlobalMessager.Register("123", () =>
                {
                    MessageBoxX.Info("我是处理123消息的!");
                });
            }
    
            public void SetName(string name)
            {
                if (MessageBoxX.Question(string.Format("要把Name值由[{0}]修改为[{1}]吗?", this.Name, name)))
                {
                    this.Name = name;
                }
            }
    
            public void ShowUser()
            {
                MessageBoxX.Info(this.Name + "---" + this.Age);
            }
    
            public void MouseOver(FrameworkElement tb)
            {
                MessageBoxX.Info("我好像摸到了" + tb.Name);
            }
        }

    5.2 测试页面Page_MVVM.xaml

      创建一个测试页面Page_MVVM,后置代码如下,在构造函数里注入View Model,在一个按钮事件里发送消息:  

        public partial class Page_MVVM : Page
        {
            public static Messenger GlobalMessager = new Messenger();
    
            public Page_MVVM()
            {
                InitializeComponent();
                //set vm
                UserViewModel uvm = new UserViewModel();
                uvm.Name = "kwong";
                uvm.Age = 30;
                this.DataContext = uvm;
    
            }
    
            private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
            {
                GlobalMessager.Notify("123");
            }
        }

      完整XAML代码:  

    <Page x:Class="Kwong.Framework.WPFTest.Page_MVVM"
          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:core="clr-namespace:XLY.Framework.Controls;assembly=XLY.Framework.Controls"
          xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
          mc:Ignorable="d" 
          d:DesignHeight="600" d:DesignWidth="800"
        Title="Page_MVVM">
        <Page.Resources>
            <Style TargetType="StackPanel">
                <Setter Property="Height" Value="80"/>
                <Setter Property="Margin" Value="3"/>
                <Setter Property="Orientation" Value="Horizontal"/>
                <Setter Property="Background" Value="{StaticResource WindowBackground}"/>
            </Style>
        </Page.Resources>
        <StackPanel Style="{x:Null}">
            <StackPanel >
                <TextBox Height="30" Width="240" Text="{Binding Name,UpdateSourceTrigger=PropertyChanged}" Margin="5 0 0 0"
                         core:ControlAttachProperty.Label="{Binding Name.Length,Mode=OneWay}" 
                         Style="{StaticResource LabelTextBox}"/>
                <TextBox Height="30" Width="240" Text="{Binding Age}" core:ControlAttachProperty.Label="Age:" 
                         Style="{StaticResource LabelTextBox}" Margin="5 0 0 0"/>
    
            </StackPanel>
            <StackPanel>
                <core:FButton Margin="5 0 0 0" Command="{Binding ShowUserCommand}">ShowUser</core:FButton>
                <core:FButton Margin="5 0 0 0" FIcon="&#xe61c;" Width="125" Click="ButtonBase_OnClick">Send Message</core:FButton>
            </StackPanel>
            <StackPanel>
                <TextBox Height="30" Width="240" x:Name="txtSetName"  core:ControlAttachProperty.Label="Name-" Margin="5 0 0 0"
                         Style="{StaticResource LabelTextBox}"></TextBox>
                <core:FButton Margin="5 0 0 0" Command="{Binding SetNameCommand}" FIcon="&#xe60c;"
                              CommandParameter="{Binding Text,ElementName=txtSetName}">SetName</core:FButton>
            </StackPanel>
            <StackPanel>
                <TextBlock VerticalAlignment="Center" Margin="5 0 0 0" Text="MoseOver" x:Name="txbMessage">
                    <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseMove">
                        <i:InvokeCommandAction Command="{Binding MouseOverCommand}" CommandParameter="{Binding ElementName=txbMessage}"></i:InvokeCommandAction>
                    </i:EventTrigger>
                    </i:Interaction.Triggers>
                </TextBlock>
            </StackPanel>
        </StackPanel>
    </Page>

    5.3 效果

     

     附录:参考引用

    WPF自定义控件与样式(1)-矢量字体图标(iconfont)

    WPF自定义控件与样式(2)-自定义按钮FButton

    WPF自定义控件与样式(3)-TextBox & RichTextBox & PasswordBox样式、水印、Label标签、功能扩展

    WPF自定义控件与样式(4)-CheckBox/RadioButton自定义样式

    WPF自定义控件与样式(5)-Calendar/DatePicker日期控件自定义样式及扩展

    WPF自定义控件与样式(6)-ScrollViewer与ListBox自定义样式

    WPF自定义控件与样式(7)-列表控件DataGrid与ListView自定义样式

    WPF自定义控件与样式(8)-ComboBox与自定义多选控件MultComboBox

    WPF自定义控件与样式(9)-树控件TreeView与菜单Menu-ContextMenu

    WPF自定义控件与样式(10)-进度控件ProcessBar自定义样 

    WPF自定义控件与样式(11)-等待/忙/正在加载状态-控件实现

    WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

    WPF自定义控件与样式(13)-自定义窗体Window & 自适应内容大小消息框MessageBox

    版权所有,文章来源:http://www.cnblogs.com/anding

    个人能力有限,本文内容仅供学习、探讨,欢迎指正、交流。

  • 相关阅读:
    解决UITableView中Cell重用机制导致内容出错的方法总结
    Hdu 1052 Tian Ji -- The Horse Racing
    Hdu 1009 FatMouse' Trade
    hdu 2037 今年暑假不AC
    hdu 1559 最大子矩阵
    hdu 1004 Let the Balloon Rise
    Hdu 1214 圆桌会议
    Hdu 1081 To The Max
    Hdu 2845 Beans
    Hdu 2955 Robberies 0/1背包
  • 原文地址:https://www.cnblogs.com/anding/p/5015067.html
Copyright © 2011-2022 走看看