WPF - 列表框项目

时间:2016-12-03 12:46:15

标签: c# wpf xaml mvvm listbox

我正在开发一个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项目和鼠标事件等的其他线程。以至于我对要走哪条路线感到困惑。

Various options

ItemContainerStyles&Commands

我想在后面的代码中有一些东西并不是那么糟糕,对于像我这样忘记了我对WPF和MVVM的了解的人来说看起来相当容易,但是因为我已经拥有了RelayCommand,BindableBase和ViewModelLocator,感觉好像将鼠标事件(双击和右键单击)连接到命令更好,但我不太确定如何。 因此,假设我在TAEngineViewModel中有一个方法OpenFile,它应该在VS编辑器中打开其名称显示在ListBox项目中的底层文件(如果它被双击),我应该在XAML中放入什么? 如何将所选的CalcFile / ParameterFile对象传递给TAEngineViewModel?

我认为右键单击事件与双击类似,如果没有,它会有什么不同?

1 个答案:

答案 0 :(得分:0)

据我所知,您需要一种机制来获取列表项单击/双击事件,并将其重定向到视图模型中的特定方法。这是我的建议:

  1. 通过nuget找到一个名为Caliburn.Micro的现代舒适MVVM框架并将其安装到您的项目中(您可以使用nuget控制台安装软件包,这里是解释链接https://www.nuget.org/packages/Caliburn.Micro),.。
  2. 如果您没有连接到TAEngineView视图的视图模型,则应创建它。此视图模型必须称为TAEngineViewModel,因为您的视图模型定位器基于命名约定。
  3. 在视图模型中创建名为OnItemClick和OnItemDoubleClick的方法。
  4. 使用特定的校准语法将点击事件重定向到视图模型中的特定方法,此处是包含更多命令https://caliburnmicro.codeplex.com/wikipage?title=Cheat%20Sheet&referringTitle=Documentation的链接。
  5. 这是一个视图模型代码:

    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方法时,特定(选定)项都会传递给视图模型。 就这些。 如果您需要更多解释,请与我们联系。 最好的问候。