我正在开发一个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;
}
}
}
}
我已经看过很多关于Listbox项目和鼠标事件等的其他线程。以至于我对要走哪条路线感到困惑。
我想在后面的代码中有一些东西并不是那么糟糕,对于像我这样忘记了我对WPF和MVVM的了解的人来说看起来相当容易,但是因为我已经拥有了RelayCommand,BindableBase和ViewModelLocator,感觉好像将鼠标事件(双击和右键单击)连接到命令更好,但我不太确定如何。 因此,假设我在TAEngineViewModel中有一个方法OpenFile,它应该在VS编辑器中打开其名称显示在ListBox项目中的底层文件(如果它被双击),我应该在XAML中放入什么? 如何将所选的CalcFile / ParameterFile对象传递给TAEngineViewModel?
我认为右键单击事件与双击类似,如果没有,它会有什么不同?
答案 0 :(得分:0)
据我所知,您需要一种机制来获取列表项单击/双击事件,并将其重定向到视图模型中的特定方法。这是我的建议:
这是一个视图模型代码:
public class TAEngineViewModel:IOnClickSupport
{
private readonly ObservableCollection<ItemWithName> _parameterList;
public ObservableCollection<ItemWithName> CalcFilesList => _parameterList;
public ObservableCollection<ItemWithName> ParameterFilesList => _parameterList;
public TAEngineViewModel()
{
_parameterList = new ObservableCollection<ItemWithName>
{
new ItemWithName(this) {Name = "AAAAAA"},
new ItemWithName(this) {Name = "BBBBBB"},
new ItemWithName(this) {Name = "CCCCCC"},
new ItemWithName(this) {Name = "DDDDDD"}
};
}
public void OnClick(object args)
{
}
public void OnDoubleClick(object args )
{
}
}
public interface IOnClickSupport
{
void OnClick(object args);
void OnDoubleClick(object args);
}
public class ItemWithName:BaseObservableObject
{
private readonly IOnClickSupport _support;
private string _name;
public ItemWithName(IOnClickSupport support)
{
_support = support;
}
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public void OnClick(object args)
{
_support.OnClick(args);
}
public void OnDoubleClick(object args)
{
_support.OnDoubleClick(args);
}
}
以下是视图代码:
<UserControl x:Class="SoCaliburnInvolvedWithEventsToCommand.TAEngineView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org"
xmlns:soCaliburnInvolvedWithEventsToCommand="clr-namespace:SoCaliburnInvolvedWithEventsToCommand"
soCaliburnInvolvedWithEventsToCommand:ViewModelLocator.AutoWireViewModel="True">
<UserControl.Resources>
<DataTemplate x:Key="DataTemplateWithTextBlockInside">
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
<DataTemplate x:Key="ItemTemplate">
<StackPanel Orientation="Horizontal">
<ContentControl cal:Message.Attach="[Event MouseDoubleClick] = [Action OnDoubleClick($eventArgs)];[Event MouseRightButtonDown] = [Action OnClick($eventArgs)]"
Content="{Binding}"
ContentTemplate="{StaticResource DataTemplateWithTextBlockInside}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<TabControl x:Name="tabControl"
Width="317"
Height="490"
Margin="19,44,-36,-234"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<TabItem Header="Parameter Files">
<ListBox Margin="20"
ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{Binding ParameterFilesList}" />
</TabItem>
<TabItem Header="Calc Files">
<ListBox Margin="20"
ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{Binding CalcFilesList}" />
</TabItem>
</TabControl>
<Label x:Name="label"
Width="367 "
Margin="19,13,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
BorderThickness="2"
Content="{Binding Path=Title}" />
</Grid></UserControl>
上面解决方案的缺点是在引擎盖下存在循环关系(视图模型被传递到每个子项以便支持具有主OnClick / OnDoubleClick逻辑的子项),你可以通过使用一种事件聚合来避免这种关系。我建议你使用RX扩展。它是一种在两个对象之间进行连接的灵活方式。要开始使用RX,您应该在VS2015中instal it via Nuget。
以下是一个例子:
public class TAEngineViewModel:IDisposable
{
private IList<IDisposable> _disposablesChildrenList = new List<IDisposable>();
private readonly ObservableCollection<ItemWithName> _parameterList;
public ObservableCollection<ItemWithName> CalcFilesList => _parameterList;
public ObservableCollection<ItemWithName> ParameterFilesList => _parameterList;
public TAEngineViewModel()
{
_parameterList = new ObservableCollection<ItemWithName>
{
new ItemWithName {Name = "AAAAAA"},
new ItemWithName {Name = "BBBBBB"},
new ItemWithName {Name = "CCCCCC"},
new ItemWithName {Name = "DDDDDD"}
};
Subscribe(_parameterList);
}
private void Subscribe(ObservableCollection<ItemWithName> parameterList)
{
foreach (var itemWithName in parameterList)
{
var onRightClickObservableSubscription = itemWithName.OnRightClickObservable.Subscribe(OnClick);
var onDoubleClickObservableSubscription = itemWithName.OnDoubleClickObservable.Subscribe(OnDoubleClick);
_disposablesChildrenList.Add(onDoubleClickObservableSubscription);
_disposablesChildrenList.Add(onRightClickObservableSubscription);
}
}
public void OnClick(IItemArguments args)
{
Debug.WriteLine($"{args.SpecificItemWithName.Name} evet {args.SpecificEventArgs.GetType().Name}");
}
public void OnDoubleClick(IItemArguments args )
{
Debug.WriteLine($"{args.SpecificItemWithName.Name} evet {args.SpecificEventArgs.GetType().Name}");
}
public void Dispose()
{
foreach (var disposable in _disposablesChildrenList)
{
disposable.Dispose();
}
}
}
public interface IItemArguments
{
ItemWithName SpecificItemWithName { get;}
object SpecificEventArgs { get;}
}
public class ItemArguments : IItemArguments
{
public ItemArguments(ItemWithName item, object args)
{
SpecificItemWithName = item;
SpecificEventArgs = args;
}
public ItemWithName SpecificItemWithName { get; }
public object SpecificEventArgs { get; }
}
public class ItemWithName:BaseObservableObject
{
private string _name;
private Subject<IItemArguments> _onDoubleClick = new Subject<IItemArguments>();
private Subject<IItemArguments> _onClick = new Subject<IItemArguments>();
public IObservable<IItemArguments> OnDoubleClickObservable;
public IObservable<IItemArguments> OnRightClickObservable;
public ItemWithName()
{
OnDoubleClickObservable = _onDoubleClick.AsObservable();
OnRightClickObservable = _onClick.AsObservable();
}
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public void OnClick(object args)
{
_onClick.OnNext(new ItemArguments(this, args));
}
public void OnDoubleClick(object args)
{
_onDoubleClick.OnNext(new ItemArguments(this, args));
}
}
如您所见,每次调用OnClick / OnDoubleClick方法时,特定(选定)项都会传递给视图模型。 就这些。 如果您需要更多解释,请与我们联系。 最好的问候。