内容简介:(文件不适合初级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 上手体验:已初具雏形
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。