AY 用wpf写个ide雏形的记录[1/15]

栏目: 编程工具 · 发布时间: 5年前

内容简介:(文件不适合初级wpf学习者看,至少看完了wpf编程宝典2遍以上,并且具有命令开发方式的人阅读,我不考虑初级阅读者了。)自己建wpf项目,nuget引用安装

(文件不适合初级wpf学习者看,至少看完了wpf编程宝典2遍以上,并且具有命令开发方式的人阅读,我不考虑初级阅读者了。)

自己建wpf项目,nuget引用安装

AY 用wpf写个ide雏形的记录[1/15]

以前我写的一篇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,我整理了文件夹

AY 用wpf写个ide雏形的记录[1/15]

一个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
    }
}

AY 用wpf写个ide雏形的记录[1/15]

主窗口是唯一的,所以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写到

AY 用wpf写个ide雏形的记录[1/15]

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种写法

AY 用wpf写个ide雏形的记录[1/15]

还有种

              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>

效果如下

AY 用wpf写个ide雏形的记录[1/15]

这样一个基本工作就组成了。

开始:

注释掉刚刚的代码,界面两层,顶部菜单栏,下方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键的,

AY 用wpf写个ide雏形的记录[1/15]

直接使用绑定方式,即时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:

AY 用wpf写个ide雏形的记录[1/15]

这种写法是单例转换器,不需要前台声明就可以用了。

打开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;
        }
    }
}

当前文件结构如下:

AY 用wpf写个ide雏形的记录[1/15]

这个时候,可以定义一个文件列表的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
  }
}

此时项目结构

AY 用wpf写个ide雏形的记录[1/15]

先模拟一个菜单列表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;
            }
        }

AY 用wpf写个ide雏形的记录[1/15]

AY 用wpf写个ide雏形的记录[1/15]

也就是说,可以通过 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=======请不要转载谢谢了。=========

推荐您阅读更多有关于“”的文章


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

网红经济3.0 自媒体时代的掘金机会

网红经济3.0 自媒体时代的掘金机会

王先明、陈建英 / 当代世界出版社 / 2016-9-1 / 42

深入剖析网红经济的商业模式和整体产业链! 正在崛起的网红经济,打造出多元化的盈利模式,催生了众多新兴的产业投资机会,成为移动互联网时候的资本新风口一起来看看 《网红经济3.0 自媒体时代的掘金机会》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具