【发布时间】:2016-12-03 12:46:14
【问题描述】:
我正在为以前在 VS 插件中的功能开发一个 VS 包扩展。
插件会将文件加载到工具栏窗口中,然后如果用户双击一个项目(这是文件的名称),该文件将在 VS 的编辑器中打开。如果用户右键单击一个项目,它会给出一个弹出菜单。所以我的问题是将列表框项的这些操作(双击和右键单击)与我现有的代码连接起来的最佳方法是什么。
对于我们使用 WPF 的扩展,但对于插件,它是 windows 窗体。 但是,我对 WPF 不是很熟悉。大约一年前,我观看了 Brian Noyes 的 Pluralsight 课程“WPF MVVM In Depth”并在扩展中实现了一些东西,但是今年大部分时间我都没有在扩展上工作。结果是我对我写的代码只有模糊的回忆,我有点困惑最好的设计是什么。
那么让我告诉你我已经拥有的东西:
这是 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>,ParameterFilesList 的类型为ObservableCollection<Parameter>。
那我已经有了这个 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;
}
}
}
}
我已经看到了很多关于列表框项目和鼠标事件等的其他线程。以至于我对要走哪条路线感到困惑。
我想在后面的代码中有一些东西并不是那么糟糕,对于像我这样忘记了一点我知道的 WPF 和 MVVM 的人来说,这看起来相当容易,但是因为我已经有了 RelayCommand、BindableBase 和 ViewModelLocator,感觉好像将鼠标事件(双击和右键单击)与命令连接起来会更好,但我不太确定如何。 所以,假设我在 TAEngineViewModel 中有一个 OpenFile 方法,它应该打开底层文件,其名称显示在 VS 编辑器的 ListBox 项目中(如果它被双击),我应该在 XAML 中放什么? 如何将选定的 CalcFile/ParameterFile 对象传递给 TAEngineViewModel?
我假设右键事件会和双击类似,如果不是,会有什么不同?
【问题讨论】: