我正在开发一个用于VS插件中的功能的VS程序包扩展。WPF - 列表框项目上的两个命令
插件会将文件加载到工具栏窗口,然后如果用户双击一个项目(这是一个文件的名称),该文件将在VS中的编辑器中打开。如果用户右键单击在一个项目上,它会弹出一个菜单。所以我的问题是关于如何将列表框项目的这些操作(双击和右键单击)与我现有的代码连接起来的最佳方式。
对于扩展我们使用WPF,但对于插件它是Windows窗体。 不过,我对WPF不是很熟悉。大约一年前,我观看了Brian Noyes的Pluralsight课程“WPF MVVM深度”,并在扩展中实现了一些内容,但是在今年的大部分时间里,我还没有开发过扩展。其结果是,我只能模糊地回忆起我编写的代码,而对于最佳设计是什么,我有点困惑。
那么让我告诉你,我已经有了:
这里是XAML文件:
<UserControl x:Class="Sym.VisualStudioExtension.Engines.TAEngineView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:behaviours="clr-namespace:Sym.VisualStudioExtension"
xmlns:local="clr-namespace:Sym.VisualStudioExtension"
local:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
d:DesignHeight="700" d:DesignWidth="400">
<Grid>
<TabControl x:Name="tabControl" HorizontalAlignment="Left" Height="490" Margin="19,44,-36,-234" VerticalAlignment="Top" Width="317">
<TabItem Header="Parameter Files">
<ListBox Margin="20" ItemsSource="{Binding ParameterFilesList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</TabItem>
<TabItem Header="Calc Files">
<ListBox Margin="20" ItemsSource="{Binding CalcFilesList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</TabItem>
</TabControl>
<Label x:Name="label" Content="{Binding Path=Title}" HorizontalAlignment="Left" Margin="19,13,0,0" VerticalAlignment="Top" Width="367
" BorderThickness="2"/>
</Grid>
CalcFilesList是ObservableCollection<CalcFile>
类型,ObservableCollection<Parameter>
型ParameterFilesList。
然后,我已经有这个RelayCommand类:
using System;
using System.Diagnostics;
using System.Windows.Input;
namespace Sym.VisualStudioExtension
{
/// <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
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> 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<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameters)
{
return _canExecute == null ? true : _canExecute(parameters);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameters)
{
_execute(parameters);
}
#endregion // ICommand Members
}
public class RelayCommand<T> : ICommand
{
#region Fields
private readonly Action<T> _execute = null;
private readonly Predicate<T> _canExecute = null;
#endregion
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command with conditional execution.
/// </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");
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_canExecute != null)
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
#endregion
}
}
这BindableBase类:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace Sym.VisualStudioExtension
{
public class BindableBase : INotifyPropertyChanged
{
protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName] string propertyName = null)
{
if (object.Equals(member, val)) return;
member = val;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
这里是ViewModelLocator:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.Practices.Unity;
using Symplexity.VisualStudioExtension.Engines;
namespace Sym.VisualStudioExtension
{
public static class ViewModelLocator
{
public static bool GetAutoWireViewModel(DependencyObject obj)
{
return (bool)obj.GetValue(AutoWireViewModelProperty);
}
public static void SetAutoWireViewModel(DependencyObject obj, bool value)
{
obj.SetValue(AutoWireViewModelProperty, value);
}
// Using a DependencyProperty as the backing store for AutoWireViewModel. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoWireViewModelProperty =
DependencyProperty.RegisterAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocator), new PropertyMetadata(false, AutoWireViewModelChanged));
private static void AutoWireViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (DesignerProperties.GetIsInDesignMode(d)) return;
var viewType = d.GetType();
var viewTypeName = viewType.FullName;
var viewModelTypeName = viewTypeName + "Model";
var viewModelType = Type.GetType(viewModelTypeName);
if (viewModelTypeName.Contains("UtilitiesViewModel"))
{
UtilitiesViewModel uViewModel = ContainerHelper.Container.Resolve<UtilitiesViewModel>();
((FrameworkElement)d).DataContext = uViewModel;
}
else
{
var viewModel = ContainerHelper.Container.Resolve(viewModelType);
((FrameworkElement)d).DataContext = viewModel;
}
}
}
}
我见过不少关于Listbox项目和鼠标事件的其他线程等等,以至于让我感到困惑要走哪条路线。
我猜有东西在后面的代码并没有那么糟糕,它看起来相当容易对像我这样谁已经忘记我知道WPF和MVVM的点点,但因为我已经有了RelayCommand,BindableBase和ViewModelLocator,所以感觉好像它将更好地将鼠标事件(双击和右键单击)与命令连接起来,但我不太确定如何。 因此,假设我在TAEngineViewModel中有一个OpenFile方法,它应该在VS Editor中打开名称显示在ListBox项目中的底层文件(如果它是双击的话),那么我应该在XAML中放置什么? 如何将所选的CalcFile/ParameterFile对象传递给TAEngineViewModel?
我假设右键单击事件将类似于双击,如果不是,它将如何不同?
你可以看看这里http://stackoverflow.com/questions/11172443/how-to-fire-a-command-on-double-click-listbox-item-using-mvvm –