一.前言

  申明: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=""
                          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}'.\nExpected: {1}\nAdding: {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}\".\nExpected: {1}\nAdding: {2}",
message,
previouslyRegisteredParameterType == null ? : ,
parameterType == null ? : ));
}
}
}
} #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 - ; i > -; --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 == )
_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 == )
return false;
}
parameterType = weakActions[].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
}

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

五.简单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("", () =>
{
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 = ;
this.DataContext = uvm; } private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
GlobalMessager.Notify("");
}
}

  完整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="" 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=""
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

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

WPF自定义控件与样式(14)-轻量MVVM模式实践的更多相关文章

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

    一.图标字体 图标字体在网页开发上运用非常广泛,具体可以网络搜索了解,网页上的运用有很多例子,如Bootstrap.但在C/S程序中使用还不多,字体图标其实就是把矢量图形打包到字体文件里,就像使用一般 ...

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

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

  3. WPF自定义控件与样式(15)-终结篇 & 系列文章索引 & 源码共享

    系列文章目录  WPF自定义控件与样式(1)-矢量字体图标(iconfont) WPF自定义控件与样式(2)-自定义按钮FButton WPF自定义控件与样式(3)-TextBox & Ric ...

  4. WPF自定义控件与样式(15)-终结篇

    原文:WPF自定义控件与样式(15)-终结篇 系列文章目录  WPF自定义控件与样式(1)-矢量字体图标(iconfont) WPF自定义控件与样式(2)-自定义按钮FButton WPF自定义控件与 ...

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. LightOJ Beginners Problems 部分题解

    相关代码请戳 https://coding.net/u/tiny656/p/LightOJ/git 1006 Hex-a-bonacci. 用数组模拟记录结果,注意取模 1008 Fibsieve's ...

  2. 关于Unity -Vuforia -Android 开发 ,平台的搭建(极品菜鸟完整版)

    一.首先安装 java jdk , 度娘 “JDK” 进入官网下载即可,链接如下: http://www.oracle.com/technetwork/java/javase/downloads/jd ...

  3. 【MySQL】事务没有提交导致 锁等待Lock wait timeout exceeded异常

    异常:Lock wait timeout exceeded; try restarting transaction 解决办法:(需要数据库最高权限) 执行select * from informati ...

  4. hdu2604(递推,矩阵快速幂)

    题目链接:hdu2604 这题重要的递推公式,找到公式就很easy了(这道题和hdu1757(题解)类似,只是这道题需要自己推公式) 可以直接找规律,推出递推公式,也有另一种找递推公式的方法:(PS: ...

  5. Linux文件目录权限总结

    代表字符 权限 对文件含义 对目录含义  r 读权限 允许查看文件内容 允许列出目录中内容 w 写权限 允许修改文件内容 允许在目录中创建或删除文件 x 执行权限 允许执行文件 允许进入目录

  6. NancyFx开发-Razor视图using外部Dll解决方案

    问题:NancyFx框架 Razor视图无法using 其他项目DLL   解决方案: Nancyfx框架Razor需要在web.config 中声明要引入的命名空间(包括system这些)   &l ...

  7. 图解集合2:LinkedList

    初识LinkedList 上一篇中讲解了ArrayList,本篇文章讲解一下LinkedList的实现. LinkedList是基于链表实现的,所以先讲解一下什么是链表.链表原先是C/C++的概念,是 ...

  8. [nRF51822] 1、一个简单的nRF51822驱动的天马4线SPI-1.77寸LCD彩屏DEMO

    最近用nRF51822写了个天马4线SPI的1.77寸LCD彩屏驱动,效果如下: 屏幕的规格资料为:http://pan.baidu.com/s/1gdfkr5L 屏幕的驱动资料为:http://pa ...

  9. 【IOS】Target membership

    Target membership是指XCode中,一个文件属于哪一个工程,在XCode左侧的工程面板中选中一个文件,在XCode右侧的属性面板中会显示其Target Membership,如下图. ...

  10. Senparc.Weixin.MP SDK 微信公众平台开发教程(七):解决用户上下文(Session)问题

    从这篇文章中我们已经了解了微信公众平台消息传递的方式,这种方式有一个先天的缺陷:不同用户的请求都来自同一个微信服务器,这使得常规的Session无法使用(始终面对同一个请求对象,况且还有对方服务器Co ...