内容简介:(文件不适合初级wpf学习者看,至少看完了wpf编程宝典2遍以上,并且具有命令开发方式的人阅读,我不考虑初级阅读者了。)自己建wpf项目,nuget引用安装
(文件不适合初级wpf学习者看,至少看完了wpf编程宝典2遍以上,并且具有命令开发方式的人阅读,我不考虑初级阅读者了。)
自己建wpf项目,nuget引用安装
以前我写的一篇avalonDock文章没啥意思,现在又有时间折腾了,以下内容自己做个笔记
====================www.ayjs.net 杨洋 wpfui.com ayui ay aaronyang=======请不要转载谢谢了。=========
总结结构:
DockingManager
LayoutRoot
LayoutPanel
后面的LayoutDocumentPaneGroup可以理解为分组
接着开始就是填内容,有两类
LayoutAnchorablePane,这控件是可以再放到全局任意一个地方
LayoutDocumentPane ,这控件主要是文档内容,一般取中间,反正就是需要足够的位置给它。
重要功能 布局的保存,把布局写到文件,然后在恢复布局。
源码代码量庞大,大致知道就好,
这个库,每一次上一个版本的代码都是开源的,目前开源的是3.4,在github的wpftoolkit里 最新的是3.8
我们直接nuget安装3.8的avalondock和它的theme
IDE基本基于命令去做,命令有作用域,有的快捷键只是某个控件的,有的是全局的,一个命令的定义离不开 RoutedUICommand 知识点
接下来知识点就是 绑定 ,不懂的先回去复习下,本文在最后一篇修改外观,前面的外观丑陋皆可以理解
示例命令:
using System.Windows.Input;
namespace Study_DOCK
{
public class AppCommand
{
private static RoutedUICommand newFile;
static AppCommand()
{
InputGestureCollection inputs = new InputGestureCollection();
AppCommand.newFile = new RoutedUICommand("新建文件", "NewFile", typeof(AppCommand), inputs);
}
public static RoutedUICommand NewFile
{
get { return AppCommand.newFile; }
}
}
}
static构造函数里面实例它,这是最简单的,不包括快捷键和执行,是否执行的。
using System.Windows.Input;
namespace Study_DOCK
{
public class AppCommand
{
static AppCommand()
{
InputGestureCollection inputs = new InputGestureCollection();
AppCommand.newFile = new RoutedUICommand("新建文件", "NewFile", typeof(AppCommand), inputs);
inputs = new InputGestureCollection();
AppCommand.loadFile = new RoutedUICommand("打开", "LoadFile", typeof(AppCommand), inputs);
inputs = new InputGestureCollection();
AppCommand.pinUnpin = new RoutedUICommand("固定\\取消固定", "PinUnpin", typeof(AppCommand), inputs);
inputs = new InputGestureCollection();
AppCommand.addMruEntry = new RoutedUICommand("添加到最近列表", "AddEntry", typeof(AppCommand), inputs);
inputs = new InputGestureCollection();
AppCommand.removeMruEntry = new RoutedUICommand("从最近列表移除", "RemoveEntry", typeof(AppCommand), inputs);
inputs = new InputGestureCollection();
AppCommand.browseURL = new RoutedUICommand("打开URL", "OpenURL", typeof(AppCommand), inputs);
inputs = new InputGestureCollection();
AppCommand.showStartPage = new RoutedUICommand("显示起始页", "ShowStartPage", typeof(AppCommand), inputs);
}
private static RoutedUICommand newFile;
public static RoutedUICommand NewFile
{
get { return AppCommand.newFile; }
}
private static RoutedUICommand loadFile;
public static RoutedUICommand LoadFile
{
get { return AppCommand.loadFile; }
}
private static RoutedUICommand pinUnpin;
public static RoutedUICommand PinUnpin
{
get { return AppCommand.pinUnpin; }
}
private static RoutedUICommand addMruEntry;
public static RoutedUICommand AddMruEntry
{
get { return AppCommand.addMruEntry; }
}
private static RoutedUICommand removeMruEntry;
public static RoutedUICommand RemoveMruEntry
{
get { return AppCommand.removeMruEntry; }
}
private static RoutedUICommand browseURL;
public static RoutedUICommand BrowseURL
{
get { return AppCommand.browseURL; }
}
private static RoutedUICommand showStartPage;
public static RoutedUICommand ShowStartPage
{
get { return AppCommand.showStartPage; }
}
}
}
接下来View是要对应Bind,也就是你们说的ViewModel,我整理了文件夹
一个Bind需要基础的通知父类,如果界面存在command,就需要一个RelayCommand
我找的RelayCommand代码如下:
namespace Study_DOCK
{
using System;
using System.Diagnostics;
using System.Windows.Input;
/// <summary>
/// A command whose sole purpose is to
/// relay its functionality to other
/// objects by invoking delegates. The
/// default return value for the CanExecute
/// method is 'true'.
///
/// Source: http://www.codeproject.com/Articles/31837/Creating-an-Internationalized-Wizard-in-WPF
/// </summary>
public class RelayCommand<T> : ICommand
{
#region Fields
private readonly Action<T> mExecute = null;
private readonly Predicate<T> mCanExecute = null;
#endregion // Fields
#region Constructors
/// <summary>
/// Class constructor
/// </summary>
/// <param name="execute"></param>
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
this.mExecute = execute;
this.mCanExecute = canExecute;
}
#endregion // Constructors
#region events
/// <summary>
/// Eventhandler to re-evaluate whether this command can execute or not
/// </summary>
public event EventHandler CanExecuteChanged
{
add
{
if (this.mCanExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (this.mCanExecute != null)
CommandManager.RequerySuggested -= value;
}
}
#endregion
#region methods
/// <summary>
/// Determine whether this pre-requisites to execute this command are given or not.
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return this.mCanExecute == null ? true : this.mCanExecute((T)parameter);
}
/// <summary>
/// Execute the command method managed in this class.
/// </summary>
/// <param name="parameter"></param>
public void Execute(object parameter)
{
this.mExecute((T)parameter);
}
#endregion methods
}
/// <summary>
/// A command whose sole purpose is to
/// relay its functionality to other
/// objects by invoking delegates. The
/// default return value for the CanExecute
/// method is 'true'.
/// </summary>
public class RelayCommand : ICommand
{
#region Fields
private readonly Action mExecute;
private readonly Func<bool> mCanExecute;
#endregion Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action execute)
: this(execute, null)
{
}
/// <summary>
/// Copy constructor
/// </summary>
/// <param name="inputRC"></param>
public RelayCommand(RelayCommand inputRC)
: this(inputRC.mExecute, inputRC.mCanExecute)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
this.mExecute = execute;
this.mCanExecute = canExecute;
}
#endregion Constructors
#region Events
/// <summary>
/// Eventhandler to re-evaluate whether this command can execute or not
/// </summary>
public event EventHandler CanExecuteChanged
{
add
{
if (this.mCanExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (this.mCanExecute != null)
CommandManager.RequerySuggested -= value;
}
}
#endregion Events
#region Methods
/// <summary>
/// Execute the attached CanExecute methode delegate (or always return true)
/// to determine whether the command managed in this object can execute or not.
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return this.mCanExecute == null ? true : this.mCanExecute();
}
/// <summary>
/// Return the attached delegate method.
/// </summary>
/// <param name="parameter"></param>
public void Execute(object parameter)
{
this.mExecute();
}
#endregion Methods
}
}
然后ViewDataContext,先弄个简单的
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Linq.Expressions;
// ReSharper disable RedundantUsingDirective
using System.Linq;
// ReSharper restore RedundantUsingDirective
#if CMNATTR
using System.Runtime.CompilerServices;
#endif
namespace Study_DOCK
{
/// <summary>
/// A base class for objects of which the properties must be observable.
/// </summary>
//// [ClassInfo(typeof(ViewModelBase))]
public class ViewDataConext : INotifyPropertyChanged /*, INotifyPropertyChanging*/
{
/// <summary>
/// Occurs after a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Provides access to the PropertyChanged event handler to derived classes.
/// </summary>
protected PropertyChangedEventHandler PropertyChangedHandler
{
get
{
return PropertyChanged;
}
}
#if !PORTABLE && !SL4
/// <summary>
/// Occurs before a property value changes.
/// </summary>
public event PropertyChangingEventHandler PropertyChanging;
/// <summary>
/// Provides access to the PropertyChanging event handler to derived classes.
/// </summary>
protected PropertyChangingEventHandler PropertyChangingHandler
{
get
{
return PropertyChanging;
}
}
#endif
/// <summary>
/// Verifies that a property name exists in this ViewModel. This method
/// can be called before the property is used, for instance before
/// calling RaisePropertyChanged. It avoids errors when a property name
/// is changed but some places are missed.
/// </summary>
/// <remarks>This method is only active in DEBUG mode.</remarks>
/// <param name="propertyName">The name of the property that will be
/// checked.</param>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
var myType = GetType();
#if NETFX_CORE
var info = myType.GetTypeInfo();
if (!string.IsNullOrEmpty(propertyName)
&& info.GetDeclaredProperty(propertyName) == null)
{
// Check base types
var found = false;
while (info.BaseType != typeof(Object))
{
info = info.BaseType.GetTypeInfo();
if (info.GetDeclaredProperty(propertyName) != null)
{
found = true;
break;
}
}
if (!found)
{
throw new ArgumentException("Property not found", propertyName);
}
}
#else
if (!string.IsNullOrEmpty(propertyName)
&& myType.GetProperty(propertyName) == null)
{
#if !SILVERLIGHT
var descriptor = this as ICustomTypeDescriptor;
if (descriptor != null)
{
if (descriptor.GetProperties()
.Cast<PropertyDescriptor>()
.Any(property => property.Name == propertyName))
{
return;
}
}
#endif
throw new ArgumentException("Property not found", propertyName);
}
#endif
}
#if !PORTABLE && !SL4
#if CMNATTR
/// <summary>
/// Raises the PropertyChanging event if needed.
/// </summary>
/// <remarks>If the propertyName parameter
/// does not correspond to an existing property on the current class, an
/// exception is thrown in DEBUG configuration only.</remarks>
/// <param name="propertyName">(optional) The name of the property that
/// changed.</param>
[SuppressMessage(
"Microsoft.Design",
"CA1030:UseEventsWhereAppropriate",
Justification = "This cannot be an event")]
public virtual void RaisePropertyChanging(
[CallerMemberName] string propertyName = null)
#else
/// <summary>
/// Raises the PropertyChanging event if needed.
/// </summary>
/// <remarks>If the propertyName parameter
/// does not correspond to an existing property on the current class, an
/// exception is thrown in DEBUG configuration only.</remarks>
/// <param name="propertyName">The name of the property that
/// changed.</param>
[SuppressMessage(
"Microsoft.Design",
"CA1030:UseEventsWhereAppropriate",
Justification = "This cannot be an event")]
public virtual void RaisePropertyChanging(
string propertyName)
#endif
{
VerifyPropertyName(propertyName);
var handler = PropertyChanging;
if (handler != null)
{
handler(this, new PropertyChangingEventArgs(propertyName));
}
}
#endif
#if CMNATTR
/// <summary>
/// Raises the PropertyChanged event if needed.
/// </summary>
/// <remarks>If the propertyName parameter
/// does not correspond to an existing property on the current class, an
/// exception is thrown in DEBUG configuration only.</remarks>
/// <param name="propertyName">(optional) The name of the property that
/// changed.</param>
[SuppressMessage(
"Microsoft.Design",
"CA1030:UseEventsWhereAppropriate",
Justification = "This cannot be an event")]
public virtual void RaisePropertyChanged(
[CallerMemberName] string propertyName = null)
#else
/// <summary>
/// Raises the PropertyChanged event if needed.
/// </summary>
/// <remarks>If the propertyName parameter
/// does not correspond to an existing property on the current class, an
/// exception is thrown in DEBUG configuration only.</remarks>
/// <param name="propertyName">The name of the property that
/// changed.</param>
[SuppressMessage(
"Microsoft.Design",
"CA1030:UseEventsWhereAppropriate",
Justification = "This cannot be an event")]
public virtual void RaisePropertyChanged(
string propertyName)
#endif
{
VerifyPropertyName(propertyName);
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#if !PORTABLE && !SL4
/// <summary>
/// Raises the PropertyChanging event if needed.
/// </summary>
/// <typeparam name="T">The type of the property that
/// changes.</typeparam>
/// <param name="propertyExpression">An expression identifying the property
/// that changes.</param>
[SuppressMessage(
"Microsoft.Design",
"CA1030:UseEventsWhereAppropriate",
Justification = "This cannot be an event")]
[SuppressMessage(
"Microsoft.Design",
"CA1006:GenericMethodsShouldProvideTypeParameter",
Justification = "This syntax is more convenient than other alternatives.")]
public virtual void RaisePropertyChanging<T>(Expression<Func<T>> propertyExpression)
{
var handler = PropertyChanging;
if (handler != null)
{
var propertyName = GetPropertyName(propertyExpression);
handler(this, new PropertyChangingEventArgs(propertyName));
}
}
#endif
/// <summary>
/// Raises the PropertyChanged event if needed.
/// </summary>
/// <typeparam name="T">The type of the property that
/// changed.</typeparam>
/// <param name="propertyExpression">An expression identifying the property
/// that changed.</param>
[SuppressMessage(
"Microsoft.Design",
"CA1030:UseEventsWhereAppropriate",
Justification = "This cannot be an event")]
[SuppressMessage(
"Microsoft.Design",
"CA1006:GenericMethodsShouldProvideTypeParameter",
Justification = "This syntax is more convenient than other alternatives.")]
public virtual void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
{
var handler = PropertyChanged;
if (handler != null)
{
var propertyName = GetPropertyName(propertyExpression);
if (!string.IsNullOrEmpty(propertyName))
{
// ReSharper disable once ExplicitCallerInfoArgument
RaisePropertyChanged(propertyName);
}
}
}
/// <summary>
/// Extracts the name of a property from an expression.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="propertyExpression">An expression returning the property's name.</param>
/// <returns>The name of the property returned by the expression.</returns>
/// <exception cref="ArgumentNullException">If the expression is null.</exception>
/// <exception cref="ArgumentException">If the expression does not represent a property.</exception>
[SuppressMessage(
"Microsoft.Design",
"CA1011:ConsiderPassingBaseTypesAsParameters",
Justification = "This syntax is more convenient than the alternatives."),
SuppressMessage(
"Microsoft.Design",
"CA1006:DoNotNestGenericTypesInMemberSignatures",
Justification = "This syntax is more convenient than the alternatives.")]
protected static string GetPropertyName<T>(Expression<Func<T>> propertyExpression)
{
if (propertyExpression == null)
{
throw new ArgumentNullException("propertyExpression");
}
var body = propertyExpression.Body as MemberExpression;
if (body == null)
{
throw new ArgumentException("Invalid argument", "propertyExpression");
}
var property = body.Member as PropertyInfo;
if (property == null)
{
throw new ArgumentException("Argument is not a property", "propertyExpression");
}
return property.Name;
}
/// <summary>
/// Assigns a new value to the property. Then, raises the
/// PropertyChanged event if needed.
/// </summary>
/// <typeparam name="T">The type of the property that
/// changed.</typeparam>
/// <param name="propertyExpression">An expression identifying the property
/// that changed.</param>
/// <param name="field">The field storing the property's value.</param>
/// <param name="newValue">The property's value after the change
/// occurred.</param>
/// <returns>True if the PropertyChanged event has been raised,
/// false otherwise. The event is not raised if the old
/// value is equal to the new value.</returns>
[SuppressMessage(
"Microsoft.Design",
"CA1006:DoNotNestGenericTypesInMemberSignatures",
Justification = "This syntax is more convenient than the alternatives."),
SuppressMessage(
"Microsoft.Design",
"CA1045:DoNotPassTypesByReference",
MessageId = "1#",
Justification = "This syntax is more convenient than the alternatives.")]
protected bool Set<T>(
Expression<Func<T>> propertyExpression,
ref T field,
T newValue)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return false;
}
#if !PORTABLE && !SL4
RaisePropertyChanging(propertyExpression);
#endif
field = newValue;
RaisePropertyChanged(propertyExpression);
return true;
}
/// <summary>
/// Assigns a new value to the property. Then, raises the
/// PropertyChanged event if needed.
/// </summary>
/// <typeparam name="T">The type of the property that
/// changed.</typeparam>
/// <param name="propertyName">The name of the property that
/// changed.</param>
/// <param name="field">The field storing the property's value.</param>
/// <param name="newValue">The property's value after the change
/// occurred.</param>
/// <returns>True if the PropertyChanged event has been raised,
/// false otherwise. The event is not raised if the old
/// value is equal to the new value.</returns>
[SuppressMessage(
"Microsoft.Design",
"CA1045:DoNotPassTypesByReference",
MessageId = "1#",
Justification = "This syntax is more convenient than the alternatives.")]
protected bool Set<T>(
string propertyName,
ref T field,
T newValue)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return false;
}
#if !PORTABLE && !SL4
RaisePropertyChanging(propertyName);
#endif
field = newValue;
// ReSharper disable ExplicitCallerInfoArgument
RaisePropertyChanged(propertyName);
// ReSharper restore ExplicitCallerInfoArgument
return true;
}
#if CMNATTR
/// <summary>
/// Assigns a new value to the property. Then, raises the
/// PropertyChanged event if needed.
/// </summary>
/// <typeparam name="T">The type of the property that
/// changed.</typeparam>
/// <param name="field">The field storing the property's value.</param>
/// <param name="newValue">The property's value after the change
/// occurred.</param>
/// <param name="propertyName">(optional) The name of the property that
/// changed.</param>
/// <returns>True if the PropertyChanged event has been raised,
/// false otherwise. The event is not raised if the old
/// value is equal to the new value.</returns>
protected bool Set<T>(
ref T field,
T newValue,
[CallerMemberName] string propertyName = null)
{
return Set(propertyName, ref field, newValue);
}
#endif
}
}
主窗口是唯一的,所以Model静态即可,wpf的静态实例不允许绑定的,你可以这样写
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Study_DOCK
{
public class MainWindowBind: ViewDataConext
{
static MainWindowBind _DataConext = new MainWindowBind();
public static MainWindowBind DataConext
{
get { return _DataConext; }
}
public MainWindowBind()
{
}
}
}
一种xaml前台,我们这里偷懒后台,MainWindow.xaml.cs写到
ViewBind中写:
private string _Hello;
public string Hello
{
get { return _Hello; }
set
{
Set("Hello", ref _Hello, value);
}
}
RelayCommand _GetHello = null;
public ICommand GetHello
{
get
{
if (_GetHello == null)
{
_GetHello = new RelayCommand(() =>
{
MessageBox.Show(Hello);
},
() => { return true; });
}
return _GetHello;
}
}
关于通知,你可以3种写法
还有种
if (_Hello == value) return;
_Hello = value;
RaisePropertyChanging("Hello");
xaml
<Window x:Class="Study_DOCK.MainWindow"
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:Study_DOCK"
mc:Ignorable="d" WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="800" Width="1200">
<Grid>
<Grid Background="Transparent" Width="200" Height="80">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Hello,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"></TextBox>
<Button Command="{Binding GetHello}" Content="获得" Grid.Row="1"></Button>
</Grid>
</Grid>
</Window>
效果如下
这样一个基本工作就组成了。
开始:
注释掉刚刚的代码,界面两层,顶部菜单栏,下方documenttab区域
<Window x:Class="Study_DOCK.MainWindow"
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:Study_DOCK"
mc:Ignorable="d" WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="800" Width="1200">
<Grid>
<!--<Grid Background="Transparent" Width="200" Height="80">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Hello,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"></TextBox>
<Button Command="{Binding GetHello}" Content="获得" Grid.Row="1"></Button>
</Grid>-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Menu>
<MenuItem Header="文件(_F)">
<MenuItem Header="新建(_N)" Command="{Binding NewCommand}"/>
<MenuItem Header="打开(_O)" Command="{Binding OpenCommand}"/>
<Separator/>
<MenuItem Header="保存(_S)" Command="{Binding ActiveDocument.SaveCommand}"/>
<MenuItem Header="另存为..." Command="{Binding ActiveDocument.SaveAsCommand}"/>
<Separator/>
<MenuItem Header="关闭(_X)" Command="{Binding ActiveDocument.CloseCommand}"/>
</MenuItem>
<MenuItem Header="工具(_T)">
<MenuItem Header="显示起始页" Command="local:AppCommand.ShowStartPage"/>
</MenuItem>
</Menu>
</Grid>
</Grid>
</Window>
知识点:字母前面加_ 下划线,是响应Alt键的,
直接使用绑定方式,即时ViewBind中没有该属性不报错。
菜单,可以直接Command,也可以Click,当然动态菜单需求来了,这里我们来写个最近文件列表
我们需要写个转换器,0个文件列表,就不显示该MenuItem
加一个Share文件夹,放入转换器的文件
namespace Study_DOCK
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Windows.Markup;
/// <summary>
/// XAML mark up extension to convert a null value into a visibility value.
/// </summary>
[MarkupExtensionReturnType(typeof(IValueConverter))]
public class ZeroToVisibilityConverter : MarkupExtension, IValueConverter
{
private static ZeroToVisibilityConverter converter;
/// <summary>
/// Standard Constructor
/// </summary>
public ZeroToVisibilityConverter()
{
}
/// <summary>
/// When implemented in a derived class, returns an object that is provided
/// as the value of the target property for this markup extension.
///
/// When a XAML processor processes a type node and member value that is a markup extension,
/// it invokes the ProvideValue method of that markup extension and writes the result into the
/// object graph or serialization stream. The XAML object writer passes service context to each
/// such implementation through the serviceProvider parameter.
/// </summary>
/// <param name="serviceProvider"></param>
/// <returns></returns>
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (converter == null)
{
converter = new ZeroToVisibilityConverter();
}
return converter;
}
#region IValueConverter
/// <summary>
/// Zero to visibility conversion method
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return System.Windows.Visibility.Collapsed;
if(value is int)
{
if((int)value == 0)
return System.Windows.Visibility.Collapsed;
}
return System.Windows.Visibility.Visible;
}
/// <summary>
/// Visibility to Zero conversion method (is disabled and will throw an exception when invoked)
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion IValueConverter
}
}
知识点2:
这种写法是单例转换器,不需要前台声明就可以用了。
打开Bind文件,写入一个列表集合,给MenuItem的ItemResource使用
一个文件列表,在menuitem存在,在起始页也存在,有图标,路径,选中,激活等属性,
每个文件都可以打开变成1个单独的documenttab
定义第一期IDE中的一些Model
定义一个Pane,格子Model
一个格子,标题,图标Uri,唯一ContentId,是否选中,是否激活,暂时先定义这么多
namespace Study_DOCK
{
using System;
public class PaneDataContext : ViewDataConext
{
public PaneDataContext()
{ }
#region Title
private string _title = null;
public string Title
{
get { return _title; }
set
{
if (_title != value)
{
_title = value;
RaisePropertyChanged("Title");
}
}
}
#endregion
public virtual Uri IconSource
{
get;
protected set;
}
#region ContentId
private string _contentId = null;
public string ContentId
{
get { return _contentId; }
set
{
if (_contentId != value)
{
_contentId = value;
RaisePropertyChanged("ContentId");
}
}
}
#endregion
#region IsSelected
private bool _isSelected = false;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
RaisePropertyChanged("IsSelected");
}
}
}
#endregion
#region IsActive
private bool _isActive = false;
public bool IsActive
{
get { return _isActive; }
set
{
if (_isActive != value)
{
_isActive = value;
RaisePropertyChanged("IsActive");
}
}
}
#endregion
}
}
加入Tool,只是额外的2个属性
namespace Study_DOCK
{
public class ToolDataContext : PaneDataContext
{
public ToolDataContext(string name)
{
Name = name;
Title = name;
}
public string Name
{
get;
private set;
}
#region IsVisible
private bool _isVisible = true;
public bool IsVisible
{
get { return _isVisible; }
set
{
if (_isVisible != value)
{
_isVisible = value;
RaisePropertyChanged("IsVisible");
}
}
}
#endregion
}
}
“最近打开文件”业务:
using System.Windows.Input;
namespace Study_DOCK
{
public abstract class FileBaseDataContext : PaneDataContext
{
private bool mIsFilePathReal = false;
/// <summary>
/// 获取/设置给定文件路径是否是真实的现有路径。
/// 这用于标识从未保存过的文件
/// 那些在MRU等中不被记住的.
/// </summary>
public bool IsFilePathReal
{
get
{
return this.mIsFilePathReal;
}
set
{
this.mIsFilePathReal = value;
}
}
abstract public string FilePath { get; protected set; }
abstract public bool IsDirty { get; set; }
#region CloseCommand
/// <summary>
/// 在AvalonDock的LayoutPanel样式用到,关闭单个Pane
/// </summary>
abstract public ICommand CloseCommand
{
get;
}
abstract public ICommand SaveCommand
{
get;
}
#endregion
}
}
IsDirty标记文件是否保存了,未保存,文件名后面,加上*号
然后定义avalondock的tabitem的DataContext
namespace Study_DOCK
{
using System;
using System.IO;
using System.Windows.Input;
class FileDataContext : FileBaseDataContext
{
public FileDataContext(string filePath)
{
FilePath = filePath;
Title = FileName;
}
public FileDataContext()
{
IsDirty = true;
Title = FileName;
}
#region FilePath
private string _filePath = null;
override public string FilePath
{
get { return _filePath; }
protected set
{
if (_filePath != value)
{
_filePath = value;
RaisePropertyChanged("FilePath");
RaisePropertyChanged("FileName");
RaisePropertyChanged("Title");
if (File.Exists(_filePath))
{
_textContent = File.ReadAllText(_filePath);
ContentId = _filePath;
}
}
}
}
#endregion
public string FileName
{
get
{
if (FilePath == null)
return "未命名" + (IsDirty ? "*" : "");
return System.IO.Path.GetFileName(FilePath) + (IsDirty ? "*" : "");
}
}
#region TextContent
private string _textContent = string.Empty;
public string TextContent
{
get { return _textContent; }
set
{
if (_textContent != value)
{
_textContent = value;
RaisePropertyChanged("TextContent");
IsDirty = true;
}
}
}
#endregion
#region IsDirty
private bool _isDirty = false;
override public bool IsDirty
{
get { return _isDirty; }
set
{
if (_isDirty != value)
{
_isDirty = value;
RaisePropertyChanged("IsDirty");
RaisePropertyChanged("FileName");
}
}
}
#endregion
#region SaveCommand
RelayCommand<object> _saveCommand = null;
override public ICommand SaveCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand<object>((p) => OnSave(p), (p) => CanSave(p));
}
return _saveCommand;
}
}
public bool CanSave(object parameter)
{
return IsDirty;
}
private void OnSave(object parameter)
{
}
#endregion
#region SaveAsCommand
RelayCommand<object> _saveAsCommand = null;
public ICommand SaveAsCommand
{
get
{
if (_saveAsCommand == null)
{
_saveAsCommand = new RelayCommand<object>((p) => OnSaveAs(p), (p) => CanSaveAs(p));
}
return _saveAsCommand;
}
}
private bool CanSaveAs(object parameter)
{
return IsDirty;
}
private void OnSaveAs(object parameter)
{
//MainWindowBind.DataConext.Save(this, true);
}
#endregion
#region CloseCommand
RelayCommand<object> _closeCommand = null;
override public ICommand CloseCommand
{
get
{
if (_closeCommand == null)
{
_closeCommand = new RelayCommand<object>((p) => OnClose(), (p) => CanClose());
}
return _closeCommand;
}
}
private bool CanClose()
{
return true;
}
private void OnClose()
{
}
#endregion
public override Uri IconSource
{
get
{
// 图标在Document Tab是不可见的
return new Uri("pack://application:,,,/Study_DOCK;component/Contents/Images/document.png", UriKind.RelativeOrAbsolute);
}
}
public void SetFileName(string f)
{
this._filePath = f;
}
}
}
当前文件结构如下:
这个时候,可以定义一个文件列表的Item了,给每条MenuItem的绑定环境
特性:列表,顺序,还可以单击操作,文件名太长,前面要省略
这里参考微软的MRU的实现
namespace ay.Control.MRU.Data
{
public class MRUEntry
{
#region constructor
/// <summary>
/// Standard Constructor
/// </summary>
public MRUEntry()
{
}
/// <summary>
/// Copy Constructor
/// </summary>
public MRUEntry(MRUEntry copyFrom)
{
if (copyFrom == null) return;
this.PathFileName = copyFrom.PathFileName;
this.IsPinned = copyFrom.IsPinned;
}
/// <summary>
/// Convinience constructor
/// </summary>
/// <param name="name"></param>
/// <param name="fullTime"></param>
public MRUEntry(string name, bool fullTime)
{
this.PathFileName = name;
this.IsPinned = fullTime;
}
#endregion constructor
#region properties
public string PathFileName { get; set; }
public bool IsPinned { get; set; }
#endregion properties
#region methods
public override string ToString()
{
return string.Format("Path {0}, IsPinned:{1}", (this.PathFileName == null ? "(null)" : this.PathFileName),
this.IsPinned);
}
#endregion methods
}
}
using Study_DOCK;
using System.Xml.Serialization;
namespace ay.Control.MRU.Data
{
public class MRUEntryVM : ViewDataConext
{
#region fields
private MRUEntry mMRUEntry;
#endregion fields
#region Constructor
/// <summary>
/// Constructor
/// </summary>
public MRUEntryVM()
{
this.mMRUEntry = new MRUEntry();
this.IsPinned = false;
}
/// <summary>
/// Constructor from model
/// </summary>
/// <param name="model"></param>
public MRUEntryVM(MRUEntry model) : this()
{
this.mMRUEntry = new MRUEntry(model);
}
/// <summary>
/// Copy constructor
/// </summary>
/// <param name="copySource"></param>
public MRUEntryVM(MRUEntryVM copySource)
: this()
{
this.mMRUEntry = new MRUEntry(copySource.mMRUEntry);
this.IsPinned = copySource.IsPinned;
}
#endregion Constructor
#region Properties
[XmlAttribute(AttributeName = "PathFileName")]
public string PathFileName
{
get
{
return this.mMRUEntry.PathFileName;
}
set
{
if (this.mMRUEntry.PathFileName != value)
{
this.mMRUEntry.PathFileName = value;
this.RaisePropertyChanged(() => this.PathFileName);
this.RaisePropertyChanged(() => this.DisplayPathFileName);
}
}
}
[XmlIgnore]
public string DisplayPathFileName
{
get
{
if (this.mMRUEntry == null)
return string.Empty;
if (this.mMRUEntry.PathFileName == null)
return string.Empty;
int n = 32;
return (mMRUEntry.PathFileName.Length > n ? mMRUEntry.PathFileName.Substring(0, 3) +
"... " + mMRUEntry.PathFileName.Substring(mMRUEntry.PathFileName.Length - n)
: mMRUEntry.PathFileName);
}
}
[XmlAttribute(AttributeName = "IsPinned")]
public bool IsPinned
{
get
{
return this.mMRUEntry.IsPinned;
}
set
{
if (this.mMRUEntry.IsPinned != value)
{
this.mMRUEntry.IsPinned = value;
this.RaisePropertyChanged(() => this.IsPinned);
}
}
}
#endregion Properties
}
}
namespace ay.Control.MRU.Data
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Collections.ObjectModel;
public class MRUList
{
#region constructor
public MRUList()
{
this.Entries = new List<MRUEntry>();
}
public MRUList(MRUList copySource)
{
if (copySource == null) return;
this.Entries = new List<MRUEntry>(copySource.Entries);
}
#endregion constructor
internal enum Spot
{
First = 0,
Last = 1
}
#region properties
[XmlArray(ElementName = "Entries", Namespace = "MRUList")]
[XmlArrayItem(ElementName = "Entry", Namespace = "MRUList")]
public List<MRUEntry> Entries { get; set; }
#endregion properties
#region AddRemoveEntries
internal bool AddEntry(MRUEntry emp,
MRUList.Spot addInSpot = MRUList.Spot.Last)
{
if(emp == null)
return false;
if(this.Entries == null)
this.Entries = new List<MRUEntry>();
switch (addInSpot)
{
case Spot.First:
this.Entries.Insert(0, new MRUEntry(emp));
return true;
case Spot.Last:
this.Entries.Add(new MRUEntry(emp));
return true;
default:
throw new NotImplementedException(addInSpot.ToString());
}
}
internal void RemoveEntry(Spot addInSpot)
{
if (this.Entries == null) return;
if (this.Entries.Count == 0) return;
switch (addInSpot)
{
case Spot.First:
this.Entries.RemoveAt(0);
break;
case Spot.Last:
this.Entries.RemoveAt(this.Entries.Count - 1);
break;
default:
break;
}
}
#endregion AddRemoveEntries
#region AddRemovePinnedEntries
internal void AddPinedEntry(MRUEntry emp)
{
if (emp == null)
return;
if (this.Entries == null)
this.Entries = new List<MRUEntry>();
this.Entries.Add(new MRUEntry(emp));
}
#endregion AddRemovePinnedEntries
internal void RemoveMruPath(string p)
{
if (this.Entries != null && p != null)
this.Entries.RemoveAll(item => p == item.PathFileName);
}
}
}
using Study_DOCK;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Xml.Serialization;
namespace ay.Control.MRU.Data
{
public class MRUListVM : ViewDataConext
{
#region Fields
private MRUSortMethod mPinEntryAtHeadOfList = MRUSortMethod.PinnedEntriesFirst;
private ObservableCollection<MRUEntryVM> mListOfMRUEntries;
private int mMaxMruEntryCount;
private RelayCommand _removeLastEntryCommand;
private RelayCommand _removeFirstEntryCommand;
#endregion Fields
#region Constructor
public MRUListVM()
{
this.mMaxMruEntryCount = 15;
this.mPinEntryAtHeadOfList = MRUSortMethod.PinnedEntriesFirst;
}
public MRUListVM(MRUSortMethod pinEntryAtHeadOfList = MRUSortMethod.PinnedEntriesFirst)
: this()
{
this.mPinEntryAtHeadOfList = pinEntryAtHeadOfList;
}
#endregion Constructor
#region Properties
[XmlAttribute(AttributeName = "MinValidMRUCount")]
public int MinValidMruEntryCount
{
get
{
return 5;
}
}
[XmlAttribute(AttributeName = "MaxValidMRUCount")]
public int MaxValidMruEntryCount
{
get
{
return 256;
}
}
[XmlAttribute(AttributeName = "MaxMruEntryCount")]
public int MaxMruEntryCount
{
get
{
return this.mMaxMruEntryCount;
}
set
{
if (this.mMaxMruEntryCount != value)
{
if (value < this.MinValidMruEntryCount || value > this.MaxValidMruEntryCount)
throw new ArgumentOutOfRangeException("MaxMruEntryCount", value, "Valid values are: value >= 5 and value <= 256");
this.mMaxMruEntryCount = value;
this.RaisePropertyChanged(() => this.MaxMruEntryCount);
}
}
}
/// <summary>
/// Get/set property to determine whether a pinned entry is shown
/// 1> at the beginning of the MRU list
/// or
/// 2> remains where it currently is.
/// </summary>
[XmlAttribute(AttributeName = "SortMethod")]
public MRUSortMethod PinSortMode
{
get
{
return this.mPinEntryAtHeadOfList;
}
set
{
if (this.mPinEntryAtHeadOfList != value)
{
this.mPinEntryAtHeadOfList = value;
this.RaisePropertyChanged(() => this.PinSortMode);
}
}
}
[XmlArrayItem("MRUList", IsNullable = false)]
public ObservableCollection<MRUEntryVM> ListOfMRUEntries
{
get
{
return this.mListOfMRUEntries;
}
set
{
if (this.mListOfMRUEntries != value)
{
this.mListOfMRUEntries = value;
this.RaisePropertyChanged(() => this.ListOfMRUEntries);
}
}
}
#region RemoveEntryCommands
public ICommand RemoveFirstEntryCommand
{
get
{
if (_removeFirstEntryCommand == null)
_removeFirstEntryCommand =
new RelayCommand(() => this.OnRemoveMRUEntry(MRUList.Spot.First));
return _removeFirstEntryCommand;
}
}
public ICommand RemoveLastEntryCommand
{
get
{
if (_removeLastEntryCommand == null)
_removeLastEntryCommand = new RelayCommand(() => this.OnRemoveMRUEntry(MRUList.Spot.Last));
return _removeLastEntryCommand;
}
}
#endregion RemoveEntryCommands
#endregion Properties
#region Methods
#region AddRemove Methods
private void OnRemoveMRUEntry(MRUList.Spot addInSpot = MRUList.Spot.Last)
{
if (this.mListOfMRUEntries == null)
return;
if (this.mListOfMRUEntries.Count == 0)
return;
switch (addInSpot)
{
case MRUList.Spot.Last:
this.mListOfMRUEntries.RemoveAt(this.mListOfMRUEntries.Count - 1);
break;
case MRUList.Spot.First:
this.mListOfMRUEntries.RemoveAt(0);
break;
default:
break;
}
//// this.NotifyPropertyChanged(() => this.ListOfMRUEntries);
}
private int CountPinnedEntries()
{
if (this.mListOfMRUEntries != null)
return this.mListOfMRUEntries.Count(mru => mru.IsPinned == true);
return 0;
}
/// <summary>
///
/// </summary>
/// <param name="bPinOrUnPinMruEntry"></param>
/// <param name="mruEntry"></param>
public bool PinUnpinEntry(bool bPinOrUnPinMruEntry, MRUEntryVM mruEntry)
{
try
{
if (this.mListOfMRUEntries == null)
return false;
int PinnedMruEntryCount = this.CountPinnedEntries();
// pin an MRU entry into the next available pinned mode spot
if (bPinOrUnPinMruEntry == true)
{
MRUEntryVM e = this.mListOfMRUEntries.Single(mru => mru.IsPinned == false && mru.PathFileName == mruEntry.PathFileName);
if (this.PinSortMode == MRUSortMethod.PinnedEntriesFirst)
this.mListOfMRUEntries.Remove(e);
e.IsPinned = true;
if (this.PinSortMode == MRUSortMethod.PinnedEntriesFirst)
this.mListOfMRUEntries.Insert(PinnedMruEntryCount, e);
PinnedMruEntryCount += 1;
//// this.NotifyPropertyChanged(() => this.ListOfMRUEntries);
return true;
}
else
{
// unpin an MRU entry into the next available unpinned spot
MRUEntryVM e = this.mListOfMRUEntries.Single(mru => mru.IsPinned == true && mru.PathFileName == mruEntry.PathFileName);
if (this.PinSortMode == MRUSortMethod.PinnedEntriesFirst)
this.mListOfMRUEntries.Remove(e);
e.IsPinned = false;
PinnedMruEntryCount -= 1;
if (this.PinSortMode == MRUSortMethod.PinnedEntriesFirst)
this.mListOfMRUEntries.Insert(PinnedMruEntryCount, e);
//// this.NotifyPropertyChanged(() => this.ListOfMRUEntries);
return true;
}
}
catch (Exception exp)
{
MessageBox.Show(this.AppName + " encountered an error when pinning an entry:" + Environment.NewLine
+ Environment.NewLine
+ exp.ToString(), "Error when pinning an MRU entry", MessageBoxButton.OK, MessageBoxImage.Error);
}
return false;
}
/// <summary>
/// Standard short-cut method to add a new unpinned entry from a string
/// </summary>
/// <param name="newEntry">File name and path file</param>
public void AddMRUEntry(string newEntry)
{
if (newEntry == null || newEntry == string.Empty)
return;
this.AddMRUEntry(new MRUEntryVM() { IsPinned = false, PathFileName = newEntry });
}
public void AddMRUEntry(MRUEntryVM newEntry)
{
if (newEntry == null) return;
try
{
if (this.mListOfMRUEntries == null)
this.mListOfMRUEntries = new ObservableCollection<MRUEntryVM>();
// Remove all entries that point to the path we are about to insert
MRUEntryVM e = this.mListOfMRUEntries.SingleOrDefault(item => newEntry.PathFileName == item.PathFileName);
if (e != null)
{
// Do not change an entry that has already been pinned -> its pinned in place :)
if (e.IsPinned == true)
return;
this.mListOfMRUEntries.Remove(e);
}
// Remove last entry if list has grown too long
if (this.MaxMruEntryCount <= this.mListOfMRUEntries.Count)
this.mListOfMRUEntries.RemoveAt(this.mListOfMRUEntries.Count - 1);
// Add model entry in ViewModel collection (First pinned entry or first unpinned entry)
if (newEntry.IsPinned == true)
this.mListOfMRUEntries.Insert(0, new MRUEntryVM(newEntry));
else
{
this.mListOfMRUEntries.Insert(this.CountPinnedEntries(), new MRUEntryVM(newEntry));
}
}
catch (Exception exp)
{
MessageBox.Show(exp.ToString(), "An error has occurred", MessageBoxButton.OK, MessageBoxImage.Error);
}
////finally
////{
//// this.NotifyPropertyChanged(() => this.ListOfMRUEntries);
////}
}
public bool RemoveEntry(string fileName)
{
try
{
if (this.mListOfMRUEntries == null)
return false;
MRUEntryVM e = this.mListOfMRUEntries.Single(mru => mru.PathFileName == fileName);
this.mListOfMRUEntries.Remove(e);
//// this.NotifyPropertyChanged(() => this.ListOfMRUEntries);
return true;
}
catch (Exception exp)
{
MessageBox.Show(this.AppName + " encountered an error when removing an entry:" + Environment.NewLine
+ Environment.NewLine
+ exp.ToString(), "Error when pinning an MRU entry", MessageBoxButton.OK, MessageBoxImage.Error);
}
return false;
}
public bool RemovePinEntry(MRUEntryVM mruEntry)
{
try
{
if (this.mListOfMRUEntries == null)
return false;
MRUEntryVM e = this.mListOfMRUEntries.Single(mru => mru.PathFileName == mruEntry.PathFileName);
this.mListOfMRUEntries.Remove(e);
//// this.NotifyPropertyChanged(() => this.ListOfMRUEntries);
return true;
}
catch (Exception exp)
{
MessageBox.Show(this.AppName + " encountered an error when removing an entry:" + Environment.NewLine
+ Environment.NewLine
+ exp.ToString(), "Error when pinning an MRU entry", MessageBoxButton.OK, MessageBoxImage.Error);
}
return false;
}
#endregion AddRemove Methods
public MRUEntryVM FindMRUEntry(string filePathName)
{
try
{
if (this.mListOfMRUEntries == null)
return null;
return this.mListOfMRUEntries.SingleOrDefault(mru => mru.PathFileName == filePathName);
}
catch (Exception exp)
{
MessageBox.Show(this.AppName + " encountered an error when removing an entry:" + Environment.NewLine
+ Environment.NewLine
+ exp.ToString(), "Error when pinning an MRU entry", MessageBoxButton.OK, MessageBoxImage.Error);
return null;
}
}
private string AppName
{
get
{
return Application.ResourceAssembly.GetName().Name;
}
}
#endregion Methods
}
}
namespace ay.Control.MRU.Data
{
/// <summary>
/// This enumeration is used to control the behaviour of pinned entries.
/// </summary>
public enum MRUSortMethod
{
/// <summary>
/// Pinned entries are sorted and displayed at the beginning of the list or just be bookmarked
/// and stay wehere they are in the list.
/// </summary>
PinnedEntriesFirst = 0,
/// <summary>
/// Pinned entries are just be bookmarked and stay wehere they are in the list. This can be useful
/// for a list of favourites (which stay if pinned) while other unpinned entries are changed as the
/// user keeps opening new items and thus, changing the MRU list...
/// </summary>
UnsortedFavourites = 1
}
}
加两个超链接控制,文件链接,单击打开,web链接打开调用浏览器
namespace ay.Control
{
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
public partial class WebHyperlink : UserControl
{
#region fields
private static readonly DependencyProperty NavigateUriProperty =
DependencyProperty.Register("NavigateUri", typeof(System.Uri), typeof(WebHyperlink));
private static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(WebHyperlink));
private static RoutedCommand mCopyUri;
private static RoutedCommand mNavigateToUri;
private System.Windows.Documents.Hyperlink mHypLink;
#endregion fields
#region constructor
static WebHyperlink()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(WebHyperlink),
new FrameworkPropertyMetadata(typeof(WebHyperlink)));
WebHyperlink.mCopyUri = new RoutedCommand("CopyUri", typeof(WebHyperlink));
CommandManager.RegisterClassCommandBinding(typeof(WebHyperlink), new CommandBinding(mCopyUri, CopyHyperlinkUri));
CommandManager.RegisterClassInputBinding(typeof(WebHyperlink), new InputBinding(mCopyUri, new KeyGesture(Key.C, ModifierKeys.Control, "Ctrl-C")));
WebHyperlink.mNavigateToUri = new RoutedCommand("NavigateToUri", typeof(WebHyperlink));
CommandManager.RegisterClassCommandBinding(typeof(WebHyperlink), new CommandBinding(mNavigateToUri, Hyperlink_CommandNavigateTo));
////CommandManager.RegisterClassInputBinding(typeof(WebHyperlink), new InputBinding(mCopyUri, new KeyGesture(Key.C, ModifierKeys.Control, "Ctrl-C")));
}
public WebHyperlink()
{
this.mHypLink = null;
}
#endregion constructor
#region properties
public static RoutedCommand CopyUri
{
get
{
return WebHyperlink.mCopyUri;
}
}
public static RoutedCommand NavigateToUri
{
get
{
return WebHyperlink.mNavigateToUri;
}
}
/// <summary>
/// Declare NavigateUri property to allow a user who clicked
/// on the dispalyed Hyperlink to navigate their with their installed browser...
/// </summary>
public System.Uri NavigateUri
{
get { return (System.Uri)GetValue(WebHyperlink.NavigateUriProperty); }
set { SetValue(WebHyperlink.NavigateUriProperty, value); }
}
public string Text
{
get { return (string)GetValue(WebHyperlink.TextProperty); }
set { SetValue(WebHyperlink.TextProperty, value); }
}
#endregion
#region Methods
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.mHypLink = this.GetTemplateChild("PART_Hyperlink") as System.Windows.Documents.Hyperlink;
Debug.Assert(this.mHypLink != null, "No Hyperlink in ControlTemplate!");
// Attach hyperlink event clicked event handler to Hyperlink ControlTemplate if there is no command defined
// Commanding allows calling commands that are external to the control (application commands) with parameters
// that can differ from whats available in this control (using converters and what not)
//
// Therefore, commanding overrules the Hyperlink.Clicked event when it is defined.
if (this.mHypLink != null)
{
if (this.mHypLink.Command == null)
this.mHypLink.RequestNavigate += this.Hyperlink_RequestNavigate;
}
}
/// <summary>
/// Process command when a hyperlink has been clicked.
/// Start a web browser and let it browse to where this points to...
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void Hyperlink_CommandNavigateTo(object sender, ExecutedRoutedEventArgs e)
{
if (sender == null || e == null) return;
e.Handled = true;
WebHyperlink whLink = sender as WebHyperlink;
if (whLink == null) return;
try
{
Process.Start(new ProcessStartInfo(whLink.NavigateUri.AbsoluteUri));
}
catch (System.Exception ex)
{
MessageBox.Show(string.Format("{0}.", ex.Message), "Error finding requested resource", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// A hyperlink has been clicked. Start a web browser and let it browse to where this points to...
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void CopyHyperlinkUri(object sender, ExecutedRoutedEventArgs e)
{
if (sender == null || e == null) return;
e.Handled = true;
WebHyperlink whLink = sender as WebHyperlink;
if (whLink == null) return;
try
{
System.Windows.Clipboard.SetText(whLink.NavigateUri.AbsoluteUri);
}
catch
{
System.Windows.Clipboard.SetText(whLink.NavigateUri.OriginalString);
}
}
/// <summary>
/// A hyperlink has been clicked. Start a web browser and let it browse to where this points to...
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
{
try
{
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
}
catch (System.Exception ex)
{
MessageBox.Show(string.Format("{0}.", ex.Message), "Error finding requested resource", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
#endregion
}
}
namespace ay.Control
{
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Reflection;
public partial class FileHyperlink : UserControl
{
#region fields
private static readonly DependencyProperty NavigateUriProperty =
DependencyProperty.Register("NavigateUri", typeof(string), typeof(FileHyperlink));
private static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(FileHyperlink));
private static RoutedCommand mCopyUri;
private static RoutedCommand mNavigateToUri;
private static RoutedCommand mOpenContainingFolder;
private System.Windows.Documents.Hyperlink mHypLink;
#endregion fields
#region constructor
static FileHyperlink()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FileHyperlink),
new FrameworkPropertyMetadata(typeof(FileHyperlink)));
FileHyperlink.mCopyUri = new RoutedCommand("CopyUri", typeof(FileHyperlink));
CommandManager.RegisterClassCommandBinding(typeof(FileHyperlink), new CommandBinding(mCopyUri, CopyHyperlinkUri));
CommandManager.RegisterClassInputBinding(typeof(FileHyperlink), new InputBinding(mCopyUri, new KeyGesture(Key.C, ModifierKeys.Control, "Ctrl-C")));
FileHyperlink.mNavigateToUri = new RoutedCommand("NavigateToUri", typeof(FileHyperlink));
CommandManager.RegisterClassCommandBinding(typeof(FileHyperlink), new CommandBinding(mNavigateToUri, Hyperlink_CommandNavigateTo));
////CommandManager.RegisterClassInputBinding(typeof(FileHyperlink), new InputBinding(mCopyUri, new KeyGesture(Key.C, ModifierKeys.Control, "Ctrl-C")));
FileHyperlink.mOpenContainingFolder = new RoutedCommand("OpenContainingFolder", typeof(FileHyperlink));
CommandManager.RegisterClassCommandBinding(typeof(FileHyperlink), new CommandBinding(mOpenContainingFolder, Hyperlink_OpenContainingFolder));
}
public FileHyperlink()
{
this.mHypLink = null;
}
#endregion constructor
#region properties
public static RoutedCommand CopyUri
{
get
{
return FileHyperlink.mCopyUri;
}
}
public static RoutedCommand NavigateToUri
{
get
{
return FileHyperlink.mNavigateToUri;
}
}
public static RoutedCommand OpenContainingFolder
{
get
{
return FileHyperlink.mOpenContainingFolder;
}
}
/// <summary>
/// Declare NavigateUri property to allow a user who clicked
/// on the dispalyed Hyperlink to navigate their with their installed browser...
/// </summary>
public string NavigateUri
{
get { return (string)GetValue(FileHyperlink.NavigateUriProperty); }
set { SetValue(FileHyperlink.NavigateUriProperty, value); }
}
public string Text
{
get { return (string)GetValue(FileHyperlink.TextProperty); }
set { SetValue(FileHyperlink.TextProperty, value); }
}
#endregion
#region Methods
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.mHypLink = this.GetTemplateChild("PART_Hyperlink") as System.Windows.Documents.Hyperlink;
Debug.Assert(this.mHypLink != null, "No Hyperlink in ControlTemplate!");
// Attach hyperlink event clicked event handler to Hyperlink ControlTemplate if there is no command defined
// Commanding allows calling commands that are external to the control (application commands) with parameters
// that can differ from whats available in this control (using converters and what not)
//
// Therefore, commanding overrules the Hyperlink.Clicked event when it is defined.
if (this.mHypLink != null)
{
if (this.mHypLink.Command == null)
this.mHypLink.RequestNavigate += this.Hyperlink_RequestNavigate;
}
}
/// <summary>
/// Process command when a hyperlink has been clicked.
/// Start a web browser and let it browse to where this points to...
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void Hyperlink_CommandNavigateTo(object sender, ExecutedRoutedEventArgs e)
{
if (sender == null || e == null) return;
e.Handled = true;
FileHyperlink whLink = sender as FileHyperlink;
if (whLink == null) return;
try
{
Process.Start(new ProcessStartInfo(whLink.NavigateUri));
////OpenFileLocationInWindowsExplorer(whLink.NavigateUri.OriginalString);
}
catch (System.Exception ex)
{
MessageBox.Show(string.Format("{0}\n'{1}'.", ex.Message, (whLink.NavigateUri == null ? string.Empty : whLink.NavigateUri.ToString())),
"Error finding requested resource", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private static void Hyperlink_OpenContainingFolder(object sender, ExecutedRoutedEventArgs e)
{
if (sender == null || e == null) return;
e.Handled = true;
FileHyperlink whLink = sender as FileHyperlink;
if (whLink == null) return;
OpenFileLocationInWindowsExplorer(whLink.NavigateUri);
}
/// <summary>
/// A hyperlink has been clicked. Start a web browser and let it browse to where this points to...
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void CopyHyperlinkUri(object sender, ExecutedRoutedEventArgs e)
{
if (sender == null || e == null) return;
e.Handled = true;
FileHyperlink whLink = sender as FileHyperlink;
if (whLink == null) return;
try
{
System.Windows.Clipboard.SetText(whLink.NavigateUri);
}
catch
{
}
}
/// <summary>
/// A hyperlink has been clicked. Start a web browser and let it browse to where this points to...
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
{
try
{
Process.Start(new ProcessStartInfo(this.NavigateUri));
}
catch (System.Exception ex)
{
MessageBox.Show(string.Format("{0}\n'{1}'.", ex.Message, (this.NavigateUri == null ? string.Empty : this.NavigateUri.ToString())),
"Error finding requested resource", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// Convinience method to open Windows Explorer with a selected file (if it exists).
/// Otherwise, Windows Explorer is opened in the location where the file should be at.
/// </summary>
/// <param name="oFileName"></param>
/// <returns></returns>
public static bool OpenFileLocationInWindowsExplorer(object oFileName)
{
string sFileName = oFileName as string;
if ((sFileName == null ? string.Empty : sFileName).Length == 0) return true;
try
{
if (System.IO.File.Exists(sFileName) == true)
{
// combine the arguments together it doesn't matter if there is a space after ','
string argument = @"/select, " + sFileName;
System.Diagnostics.Process.Start("explorer.exe", argument);
return true;
}
else
{
string sParentDir = System.IO.Directory.GetParent(sFileName).FullName;
if (System.IO.Directory.Exists(sParentDir) == false)
MessageBox.Show(string.Format("The directory '{0}' does not exist or cannot be accessed.", sParentDir),
"Error finding requested resource", MessageBoxButton.OK, MessageBoxImage.Error);
else
{
// combine the arguments together it doesn't matter if there is a space after ','
string argument = @"/select, " + sParentDir;
System.Diagnostics.Process.Start("explorer.exe", argument);
return true;
}
}
}
catch (System.Exception ex)
{
MessageBox.Show(string.Format("{0}\n'{1}'.", ex.Message, (sFileName == null ? string.Empty : sFileName)),
"Error finding requested resource", MessageBoxButton.OK, MessageBoxImage.Error);
}
return true;
}
#endregion
}
}
此时项目结构
先模拟一个菜单列表MRUListVM,我们用RecentFilesBind包装下
namespace Study_DOCK
{
using ay.Control.MRU.Data;
using System;
using System.IO;
public class RecentFilesBind : ToolDataContext
{
private MRUListVM mMruList;
public const string ToolContentId = "RecentFilesTool";
public RecentFilesBind()
: base("最近文件列表")
{
ContentId = ToolContentId;
this.mMruList = new MRUListVM();
}
public override Uri IconSource
{
get
{
return new Uri("pack://application:,,,/Study_DOCK;component/Contents/Image/NoPin16.png", UriKind.RelativeOrAbsolute);
}
}
public MRUListVM MruList
{
get
{
return this.mMruList;
}
private set
{
if (this.mMruList != value)
{
this.mMruList = value;
this.RaisePropertyChanged(() => this.MruList);
}
}
}
public void AddNewEntryIntoMRU(string filePath)
{
if (this.MruList.FindMRUEntry(filePath) == null)
{
MRUEntryVM e = new MRUEntryVM() { IsPinned = false, PathFileName = filePath };
this.MruList.AddMRUEntry(e);
this.RaisePropertyChanged(() => this.MruList);
}
}
}
}
打开MainWindowBind.cs
public MainWindowBind()
{
this.RecentFiles.MruList.AddMRUEntry(@"E:\AYUI7\AYUI7\WpfApplication1\App,xaml");
this.RecentFiles.MruList.AddMRUEntry(@"E:\AYUI7\AYUI7\WpfApplication1\App,xaml.cs");
this.RecentFiles.MruList.AddMRUEntry(@"E:\AYUI7\AYUI7\WpfApplication1\MainWindow.xaml");
this.RecentFiles.MruList.AddMRUEntry(@"E:\AYUI7\AYUI7\WpfApplication1\MainWindow.xaml.cs");
this.RecentFiles.MruList.AddMRUEntry(@"E:\AYUI7\AYUI7\WpfApplication1\WpfApplication1.csproj");
}
private RecentFilesBind _recentFiles = null;
public RecentFilesBind RecentFiles
{
get
{
if (_recentFiles == null)
_recentFiles = new RecentFilesBind();
return _recentFiles;
}
}
也就是说,可以通过 this.RecentFiles.MruList.AddMRUEntry增加新的打开文件了
<MenuItem ItemsSource="{Binding RecentFiles.MruList.ListOfMRUEntries}" Header="最近打开文件"
Visibility="{Binding Path=RecentFiles.MruList.ListOfMRUEntries, Mode=OneWay, Converter={local:ZeroToVisibilityConverter}}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding DisplayPathFileName, Mode=OneWay}" />
<Setter Property="Command" Value="local:AppCommand.LoadFile" />
<Setter Property="CommandParameter" Value="{Binding PathFileName, Mode=OneWay}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
灰色不可用,因为命令,CanExecute的问题,实现就好了
路径处理显示逻辑如下:
int n = 32;
return (mMRUEntry.PathFileName.Length > n ? mMRUEntry.PathFileName.Substring(0, 3) +
"... " + mMRUEntry.PathFileName.Substring(mMRUEntry.PathFileName.Length - n)
: mMRUEntry.PathFileName);
这样动态菜单也完成了,后面执行打开命令,新建保存后命令就可以加入这个几何,保存xml到本地,下次打开恢复
点写到这里,后面我们新建tab,打开tab,
参考文章: 查看
====================www.ayjs.net 杨洋 wpfui.com ayui ay aaronyang=======请不要转载谢谢了。=========
推荐您阅读更多有关于“”的文章
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 我用Python实现了一个小说网站雏形
- 带你从0开发图表库系列-初具雏形
- “智慧交通”初现雏形,城市出行难题迎刃而解
- 木兰编程语言重现——功能初具雏形,添加中文报错信息
- 为木兰开发环境雏形添加输入补全,功能测试大提速
- Beta 版三星 Linux on DeX 上手体验:已初具雏形
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。