我的ViewModel知道View,我该如何解决这个问题?

时间:2012-06-20 14:57:29

标签: c# events mvvm command viewmodel

我认为代码是不言自明的。

<Interactivity:Interaction.Triggers>
    <Interactivity:EventTrigger EventName="Deleting">
        <MVVMLight:EventToCommand Command="{Binding Deleting, Mode=OneWay}" 
                                  PassEventArgsToCommand="True" />
    </Interactivity:EventTrigger>
</Interactivity:Interaction.Triggers>

我有自己的自定义控件和删除事件,并希望将其绑定到ViewModel中的命令。

但在视图模型中,我现在要么

public void OnDeleting(EventArgs args)
{
    var e = args as MapDeletingEventArgs;

    if (e == null) 
        throw new ArgumentNullException("args");

    Database.Delete(e.Maps);
    Database.Commit();
}

或更糟

public void OnDeleting(MapDeletingEventArgs args)
{
    if (args == null) 
        throw new ArgumentNullException("args");

    Database.Delete(args.Maps);
    Database.Commit();
}

我知道在ViewModel中拥有视图逻辑有多糟糕。我能想到没有更好的方法,有人建议吗?正如你所看到的,我使用框架MVVMLight。

2 个答案:

答案 0 :(得分:2)

这可以通过ICommand实现来实现,该实现将Map实例作为命令参数:

//WARNING: all code typed in SO window
public class DeleteMapsCommand : ICommand
{
    private Database _db;

    public DeleteMapsCommand(Database db)
    {
        _db = db;
    }

    public void CanExecute(object parameter)
    {
        //only allow delete if the parameter passed in is a valid Map
        return (parameter is Map);
    }

    public void Execute(object parameter)
    {
        var map = parameter as Map;
        if (map == null) return;

        _db.Delete(map);
        _db.Commit();
    }

    public event EventHandler CanExecuteChanged; //ignore this for now
}

然后在视图模型中创建公共属性以公开命令的实例

public class ViewModel
{
    public ViewModel() {
        //get the Database reference from somewhere?
        this.DeleteMapCommand = new DeleteMapsCommand(this.Database); 
    }

    public ICommand DeleteMapCommand { get; private set; }
}

最后,您需要将操作绑定到命令属性将命令属性绑定到要删除的映射。你还没有真正给我足够的XAML来说明在你的情况下应该怎么做,但是你可以用ListBox做下面的事情:

<ListBox x:Name="ListOfMaps" ItemsSource="{Binding AllTheMaps}" />
<Button Command="{Binding DeleteMapCommand}" CommandParameter="{Binding SelectedItem, ElementName=ListOfMaps}">Delete Selected Map</Button>

<强>更新

要将命令附加到事件,您可以使用附加属性:

public static class Helper
{
    public static IComparable GetDeleteMapCommand(DependencyObject obj)
    {
        return (IComparable)obj.GetValue(DeleteMapCommandProperty);
    }

    public static void SetDeleteMapCommand(DependencyObject obj, IComparable value)
    {
        obj.SetValue(DeleteMapCommandProperty, value);
    }

    public static readonly DependencyProperty DeleteMapCommandProperty =
        DependencyProperty.RegisterAttached("DeleteMapCommand", typeof(IComparable), typeof(Helper), new UIPropertyMetadata(null, OnDeleteMapCommandChanged));

    private static void OnDeleteMapCommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        //when we attach the command, grab a reference to the control
        var mapControl = sender as MapControl;
        if (mapControl == null) return;

        //and the command
        var command = GetDeleteMapCommand(sender);
        if (command == null) return;

        //then hook up the event handler
        mapControl.Deleting += (o,e) =>
        {
            if (command.CanExecute(e.Maps))
                command.Execute(e.Maps);
        };
    }
}

然后你需要像这样绑定命令:

<MapControl local:Helper.DeleteMapCommand="{Binding DeleteMapCommand}" />

现在,您的视图模型没有引用特定于视图的类型。

答案 1 :(得分:0)

如果你不想把你的EventArgs关闭到你的viewmodel,你可以尝试使用一个行为(这类似于Steve Greatrex的解决方案,但使用Blend SDK的行为代替):

以下是我在其中一个应用程序中使用的示例。

首先,这是我的自定义行为基类:

/// <summary>
/// "Better" Behavior base class which allows for safe unsubscribing. The default Behavior class does not always call <see cref="Behavior.OnDetaching"/>
/// </summary>
/// <typeparam name="T">The dependency object this behavior should be attached to</typeparam> 
public abstract class ZBehaviorBase<T> : Behavior<T> where T : FrameworkElement
{
    private bool _isClean = true;

    /// <summary>
    /// Called after the behavior is attached to an AssociatedObject.
    /// </summary>
    /// <remarks>Override this to hook up functionality to the AssociatedObject.</remarks>
    protected sealed override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.Unloaded += OnAssociatedObjectUnloaded;
        _isClean = false;     
        ValidateRequiredProperties();     
        Initialize();
    }

    /// <summary>
    /// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
    /// </summary>
    /// <remarks>Override this to unhook functionality from the AssociatedObject.</remarks>
    protected sealed override void OnDetaching()
    {
        CleanUp();     
        base.OnDetaching();
    }

    /// <summary>
    /// Validates the required properties. This method is called when the object is attached, but before
    /// the <see cref="Initialize"/> is invoked.
    /// </summary>
    protected virtual void ValidateRequiredProperties()
    {
    }

    /// <summary>
    /// Initializes the behavior. This method is called instead of the <see cref="OnAttached"/> which is sealed
    /// to protect the additional behavior.
    /// </summary>
    protected abstract void Initialize();        

    /// <summary>
    /// Uninitializes the behavior. This method is called when <see cref="OnDetaching"/> is called, or when the
    /// <see cref="AttachedControl"/> is unloaded.
    /// <para />
    /// If dependency properties are used, it is very important to use <see cref="ClearValue"/> to clear the value
    /// of the dependency properties in this method.
    /// </summary>
    protected abstract void Uninitialize();

    /// <summary>
    /// Called when the <see cref="AssociatedObject"/> is unloaded.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    private void OnAssociatedObjectUnloaded(object sender, EventArgs e)
    {
        CleanUp();
    }

    /// <summary>
    /// Actually cleans up the behavior because <see cref="OnDetaching"/> is not always called.
    /// </summary>
    /// <remarks>
    /// This is based on the blog post: http://dotnetbyexample.blogspot.com/2011/04/safe-event-detachment-pattern-for.html.
    /// </remarks>
    private void CleanUp()
    {
        if (_isClean)
        {
            return;
        }

        _isClean = true;

        if (AssociatedObject != null)
        {
            AssociatedObject.Unloaded -= OnAssociatedObjectUnloaded;
        }

        Uninitialize();
    }
}

现在,我的具体实现用于将命令附加到TextBlock的“click”事件

public class TextBlockClickCommandBehavior : ZBehaviorBase<TextBlock>
{
    public ICommand ClickCommand
    {
        get { return (ICommand)GetValue(ClickCommandProperty); }
        set { SetValue(ClickCommandProperty, value); }
    }

    public static readonly DependencyProperty ClickCommandProperty =
        DependencyProperty.Register("ClickCommand", typeof(ICommand), typeof(TextBlockClickCommandBehavior));

    protected override void Initialize()
    {
        this.AssociatedObject.MouseLeftButtonUp += new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonUp);
    }

    protected override void Uninitialize()
    {
        if (this.AssociatedObject != null)
        {
            this.AssociatedObject.MouseLeftButtonUp -= new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonUp);
        }
    }

    void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        // if you want to pass a command param to CanExecute, need to add another dependency property to bind to
        if (ClickCommand != null && ClickCommand.CanExecute(null))
        {
            ClickCommand.Execute(null);
        }
    }
}

我这样使用它:

<!--Make the TextBlock for "Item" clickable to display the item search window-->
<TextBlock x:Name="itemTextBlock" Text="Item:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Column="2" FontWeight="Bold">
    <e:Interaction.Behaviors>
        <ZViewModels:TextBlockClickCommandBehavior ClickCommand="{Binding Path=ItemSearchCommand}"/>
    </e:Interaction.Behaviors>
</TextBlock>

现在,在您的情况下,您不想将NULL传递给命令的execute方法,而是要传递参数'Maps collection