Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/263.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何在不使用代码隐藏的情况下在用户控件上实现命令?_C#_Wpf_Mvvm_User Controls_Icommand - Fatal编程技术网

C# 如何在不使用代码隐藏的情况下在用户控件上实现命令?

C# 如何在不使用代码隐藏的情况下在用户控件上实现命令?,c#,wpf,mvvm,user-controls,icommand,C#,Wpf,Mvvm,User Controls,Icommand,我只是设法让我的WPF自定义消息窗口按我的预期工作。。。几乎: MessageWindow window; public void MessageBox() { var messageViewModel = new MessageViewModel("Message Title", "This message is showing up because of WPF databinding with ViewModel. Yay!

我只是设法让我的WPF自定义消息窗口按我的预期工作。。。几乎:

    MessageWindow window;

    public void MessageBox()
    {
        var messageViewModel = new MessageViewModel("Message Title",
            "This message is showing up because of WPF databinding with ViewModel. Yay!",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec fermentum elit non dui sollicitudin convallis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Integer sed elit magna, non dignissim est. Morbi sed risus id mi pretium facilisis nec non purus. Cras mattis leo sapien. Mauris at erat sapien, vitae commodo turpis. Nam et dui quis mauris mattis volutpat. Donec risus purus, aliquam ut venenatis id, varius vel mauris.");
        var viewModel = new MessageWindowViewModel(messageViewModel, BottomPanelButtons.YesNoCancel);
        window = new MessageWindow(viewModel);
        viewModel.MessageWindowClosing += viewModel_MessageWindowClosing;
        window.ShowDialog();

        var result = viewModel.DialogResult;
        System.Windows.MessageBox.Show(string.Format("result is {0}", result));
    }

    void viewModel_MessageWindowClosing(object sender, EventArgs e)
    {
        window.Close();
    }
在引擎盖下,有一个“BottomPanel”用户控件,它只创建一组按钮,这些按钮的“可见性”属性由MessageWindowViewModel控制(通过属性获取程序,例如“IsOkButtonVisible”,其本身由传递给viewmodel构造函数的“BottomPanelButtons”枚举的值确定)

虽然这满足了我的要求,即能够显示具有可折叠详细信息的消息窗口,并在底部显示一组可配置的按钮,但我对我将BottomPanel控件(或者更确切地说,将其viewmodel)中最初需要的所有功能放入MessageWindowViewModel类的方式感到失望:

    public MessageWindowViewModel(MessageViewModel messageViewModel, BottomPanelButtons buttons)
    {
        _messageViewModel = messageViewModel;
        _abortCommand = new DelegateCommand(ExecuteAbortCommand, CanExecuteAbortCommand);
        _applyCommand = new DelegateCommand(ExecuteApplyCommand, CanExecuteApplyCommand);
        _cancelCommand = new DelegateCommand(ExecuteCancelCommand, CanExecuteCancelCommand);
        _closeCommand = new DelegateCommand(ExecuteCloseCommand, CanExecuteCloseCommand);
        _ignoreCommand = new DelegateCommand(ExecuteIgnoreCommand, CanExecuteIgnoreCommand);
        _noCommand = new DelegateCommand(ExecuteNoCommand, CanExecuteNoCommand);
        _okCommand = new DelegateCommand(ExecuteOkCommand, CanExecuteOkCommand);
        _retryCommand = new DelegateCommand(ExecuteRetryCommand, CanExecuteRetryCommand);
        _yesCommand = new DelegateCommand(ExecuteYesCommand, CanExecuteYesCommand);
        Buttons = buttons;
    }

    /// <summary>
    /// Gets/sets a value that determines what buttons appear in the bottom panel.
    /// </summary>
    public BottomPanelButtons Buttons { get; set; }

    public bool IsCloseButtonVisible { get { return Buttons == BottomPanelButtons.ApplyClose || Buttons == BottomPanelButtons.Close; } }
    public bool IsOkButtonVisible { get { return Buttons == BottomPanelButtons.Ok || Buttons == BottomPanelButtons.OkCancel; } }
    public bool IsCancelButtonVisible { get { return Buttons == BottomPanelButtons.OkCancel || Buttons == BottomPanelButtons.RetryCancel || Buttons == BottomPanelButtons.YesNoCancel; } }
    public bool IsYesButtonVisible { get { return Buttons == BottomPanelButtons.YesNo || Buttons == BottomPanelButtons.YesNoCancel; } }
    public bool IsNoButtonVisible { get { return IsYesButtonVisible; } }
    public bool IsApplyButtonVisible { get { return Buttons == BottomPanelButtons.ApplyClose; } }
    public bool IsAbortButtonVisible { get { return Buttons == BottomPanelButtons.AbortRetryIgnore; } }
    public bool IsRetryButtonVisible { get { return Buttons == BottomPanelButtons.AbortRetryIgnore || Buttons == BottomPanelButtons.RetryCancel; } }
    public bool IsIgnoreButtonVisible { get { return Buttons == BottomPanelButtons.AbortRetryIgnore; } }

    public ICommand AbortCommand { get { return _abortCommand; } }
    public ICommand ApplyCommand { get { return _applyCommand; } }
    public ICommand CancelCommand { get { return _cancelCommand; } }
    public ICommand CloseCommand { get { return _closeCommand; } }
    public ICommand IgnoreCommand { get { return _ignoreCommand; } }
    public ICommand NoCommand { get { return _noCommand; } }
    public ICommand OkCommand { get { return _okCommand; } }
    public ICommand RetryCommand { get { return _retryCommand; } }
    public ICommand YesCommand { get { return _yesCommand; } }

    public string AbortButtonText { get { return resx.AbortButtonText; } }
    public string ApplyButtonText { get { return resx.ApplyButtonText; } }
    public string CancelButtonText { get { return resx.CancelButtonText; } }
    public string CloseButtonText { get { return resx.CloseButtonText; } }
    public string IgnoreButtonText { get { return resx.IgnoreButtonText; } }
    public string NoButtonText { get { return resx.NoButtonText; } }
    public string OkButtonText { get { return resx.OkButtonText; } }
    public string RetryButtonText { get { return resx.RetryButtonText; } }
    public string YesButtonText { get { return resx.YesButtonText; } }

    private ICommand _abortCommand; 
    private ICommand _applyCommand; 
    private ICommand _cancelCommand; 
    private ICommand _closeCommand; 
    private ICommand _ignoreCommand; 
    private ICommand _noCommand; 
    private ICommand _okCommand; 
    private ICommand _retryCommand; 
    private ICommand _yesCommand;
现在这是可行的,但我觉得很难看。我的意思是,我想要的是一个BottomPanelViewModel类,它包含所有BottomPanel的功能。我唯一喜欢的是,我没有代码隐藏(除了在MessageView类中使用MessageViewModel的构造函数,设置DataContext属性)

所以问题是:有没有可能重构这段代码,让我最终得到一个可重用的BottomPanel控件,一个将其功能嵌入到自己的viewmodel中并拥有自己命令的控件?想法是让底部面板控件上的命令和包含窗口的ViewModel中的处理程序。。。还是说这太牵强了


我已经尝试了很多方法(依赖属性、静态命令等等),但我现在拥有的是唯一能够让它在没有代码落后的情况下工作的方法。我相信有一种更好、更专注的做事方式——请原谅我的WPF不好,这个“消息框”窗口是我的WPF“Hello World!”的第一个项目……

根据我个人的经验,我有一些建议

首先,您可以为应该由
ViewModel
执行的任何视图逻辑创建一个接口

其次,我发现更好的方法不是在
ViewModel
中使用*ButtonVisibility,而是指定
ViewModel
的“模式”,并在视图层中使用
ValueConverter
触发器来指定在该模式下显示的内容。这使得您的ViewModel不会(通过一个bug)意外地进入一个无效的状态,因为它提供了一个类似于scenerio的场景

IsYesButtonVisible = true;
IsAbortButtonVisible = true;
我知道您的属性没有setter,但是维护代码的人可以很容易地添加它们,这只是一个简单的示例

对于你的情况,我们只需要第一个

只需创建一个您想要使用的接口。你可以根据自己的喜好重新命名,但这里是他的一个例子

public interface IDialogService
{
    public void Inform(string message);
    public bool AskYesNoQuestion(string question, string title);
}
然后在视图层中,您可以创建一个在整个应用程序中都相同的实现

public class DialogService
{
    public void Inform(string message)
    {
        MessageBox.Show(message);
    }

    public bool AskYesNoQuestion(string question)
    {
        return MessageBox.Show(question, title, MessageBoxButton.YesNo) ==         
                   MessageBoxResult.Yes
    }
}
然后您可以在任何
视图模型中使用

public class FooViewModel
{
    public FooViewModel(IDialogService dialogService)
    {
        DialogService = dialogService;
    }

    public IDialogService DialogService { get; set; }

    public DelegateCommand DeleteBarCommand
    {
        get
        {
            return new DelegateCommand(DeleteBar);
        }
    }

    public void DeleteBar()
    {
        var shouldDelete = DialogService.AskYesNoQuestion("Are you sure you want to delete bar?", "Delete Bar");
        if (shouldDelete)
        {
            Bar.Delete();
        }
    }

    ...
}

我最终使用了@JerKimball建议的
RoutedCommand
。在我的搜索中,我看到了几十种实现这一点的方法,可能都是正确的,但没有一种让我满意

我正在将对我有用的内容发布为社区维基:

BottomPanel
控件最终没有-minimal-code behind,因为无法将
CommandBindings
绑定到ViewModel(因为命令不是
dependencProperty
)。因此,代码隐藏仅调用“主机”视图模型,
Execute
CanExecute
方法的实际实现驻留在该模型中:

public partial class BottomPanel : UserControl
{
    public BottomPanel()
    {
        InitializeComponent();
    }

    private void ExecuteOkCommand(object sender, ExecutedRoutedEventArgs e)
    {
        if (DataContext == null) return;
        var viewModel = ((BottomPanelViewModel)DataContext).Host;
        if (viewModel != null) viewModel.ExecuteOkCommand(sender, e);
    }

    private void CanExecuteOkCommand(object sender, CanExecuteRoutedEventArgs e)
    {
        if (DataContext == null) return;
        var viewModel = ((BottomPanelViewModel)DataContext).Host;
        if (viewModel != null) viewModel.CanExecuteOkCommand(sender, e);
    }
    ...
}
为了避免控件与特定ViewModel紧密耦合,我创建了一个界面:

public interface IHasBottomPanel
{
    event EventHandler WindowClosing;
    DialogResult DialogResult { get; set; }
    BottomPanelViewModel BottomPanelViewModel { get; set; }

    void ExecuteOkCommand(object sender, ExecutedRoutedEventArgs e);
    ...

    void CanExecuteOkCommand(object sender, CanExecuteRoutedEventArgs e);
    ...
}
可能值得注意的是,我使用的
对话框result
是我自己对它的解释(更接近WinForms的功能),因为一个简单的
bool
无法满足需要-当用户“X”离开窗口时返回“未定义”值:

public enum DialogResult
{
    Undefined,
    Abort,
    Apply,
    Cancel,
    Close,
    Ignore,
    No,
    Ok,
    Retry,
    Yes
}
因此,回到
BottomPanel
控件,在XAML中,我可以如下定义命令绑定:

<UserControl.CommandBindings>
    <CommandBinding Command="{x:Static local:BottomPanelViewModel.OkCommand}"
                    Executed="ExecuteOkCommand"
                    CanExecute="CanExecuteOkCommand"/>
    ...
此ViewModel还包含代码隐藏引用的
Host
属性,该属性间接公开将处理以下命令的ViewModel:

    /// <summary>
    /// Gets the host view model.
    /// </summary>
    public IHasBottomPanel Host { get; private set; }

    /// Gets a value that determines what buttons appear in the bottom panel.
    /// </summary>
    public BottomPanelButtons Buttons { get; private set; }

    /// <summary>
    /// Creates a new ViewModel for a <see cref="BottomPanel"/> control.
    /// </summary>
    /// <param name="buttons">An enum that determines which buttons are shown.</param>
    /// <param name="host">An interface representing the ViewModel that will handle the commands.</param>
    public BottomPanelViewModel(BottomPanelButtons buttons, IHasBottomPanel host)
    {
        Buttons = buttons;
        Host = host;
    }
所以我得到了我想要的:“主机”视图模型控制
底部面板中所有命令的
执行
可执行
实现,并且可以在另一个“主机”上以不同方式实现。这里有一种配置ViewModel的方法,以便视图显示ProgressBar控件,在这种情况下,“Ok”按钮仅在ProgressBar的值达到最大值时才启用(同时启用“Cancel”按钮,并在启用“Ok”时禁用)

然后,我可以实现自己的
MsgBox
静态类,并为显示给用户的各种消息公开按钮和图标的各种配置:

public static class MsgBox
{
    private static DialogResult MessageBox(MessageViewModel messageViewModel, BottomPanelButtons buttons)
    {
        var viewModel = new MessageWindowViewModel(messageViewModel, buttons);
        var window = new MessageWindow(viewModel);
        window.ShowDialog();
        return viewModel.DialogResult;
    }

    /// <summary>
    /// Displays an informative message to the user.
    /// </summary>
    /// <param name="title">The message's title.</param>
    /// <param name="message">The message's body.</param>
    /// <returns>Returns <see cref="DialogResult.Ok"/> if user closes the window by clicking the Ok button.</returns>
    public static DialogResult Info(string title, string message)
    {
        return Info(title, message, string.Empty);
    }

    /// <summary>
    /// Displays an informative message to the user.
    /// </summary>
    /// <param name="title">The message's title.</param>
    /// <param name="message">The message's body.</param>
    /// <param name="details">The collapsible message's details.</param>
    /// <returns>Returns <see cref="DialogResult.Ok"/> if user closes the window by clicking the Ok button.</returns>
    public static DialogResult Info(string title, string message, string details)
    {
        var viewModel = new MessageViewModel(title, message, details, MessageIcons.Info);
        return MessageBox(viewModel, BottomPanelButtons.Ok);
    }

    /// <summary>
    /// Displays an error message to the user, with stack trace as message details.
    /// </summary>
    /// <param name="title">The message's title.</param>
    /// <param name="exception">The exception to report.</param>
    /// <returns>Returns <see cref="DialogResult.Ok"/> if user closes the window by clicking the Ok button.</returns>
    public static DialogResult Error(string title, Exception exception)
    {
        var viewModel = new MessageViewModel(title, exception.Message, exception.StackTrace, MessageIcons.Error);
        return MessageBox(viewModel, BottomPanelButtons.Ok);
    }
    ...
}
公共静态类MsgBox
{
专用静态对话框Result MessageBox(MessageViewModel MessageViewModel,底部面板按钮)
{
var viewModel=newmessagewindowviewmodel(messageViewModel,按钮);
var窗口=新消息窗口(viewModel);
ShowDialog();
返回viewModel.DialogResult;
}
/// 
///向用户显示信息性消息。
/// 
///消息的标题。
///消息的正文。
///如果用户通过单击“确定”按钮关闭窗口,则返回。
公共静态对话框结果信息(字符串标题、字符串消息)
{
返回信息(标题、消息、字符串.空);
}
/// 
///向用户显示信息性消息。
/// 
///消息的标题。
///消息的正文。
///可折叠邮件的详细信息。
///如果用户通过单击“确定”按钮关闭窗口,则返回。
公共静态对话框结果信息(字符串标题、字符串消息、字符串详细信息)
{
var viewModel=新消息
    /// <summary>
    /// Gets the host view model.
    /// </summary>
    public IHasBottomPanel Host { get; private set; }

    /// Gets a value that determines what buttons appear in the bottom panel.
    /// </summary>
    public BottomPanelButtons Buttons { get; private set; }

    /// <summary>
    /// Creates a new ViewModel for a <see cref="BottomPanel"/> control.
    /// </summary>
    /// <param name="buttons">An enum that determines which buttons are shown.</param>
    /// <param name="host">An interface representing the ViewModel that will handle the commands.</param>
    public BottomPanelViewModel(BottomPanelButtons buttons, IHasBottomPanel host)
    {
        Buttons = buttons;
        Host = host;
    }
public class MessageWindowViewModel : ViewModelBase, IHasBottomPanel
{
    /// <summary>
    /// Gets/sets ViewModel for the message window's content.
    /// </summary>
    public MessageViewModel ContentViewModel { get { return _messageViewModel; } }
    private MessageViewModel _messageViewModel;

    public MessageWindowViewModel()
        : this(new MessageViewModel())
    { }

    public MessageWindowViewModel(MessageViewModel viewModel)
        : this(viewModel, BottomPanelButtons.Ok)
    { }

    public MessageWindowViewModel(MessageViewModel messageViewModel, BottomPanelButtons buttons)
    {
        _messageViewModel = messageViewModel;
        // "this" is passed as the BottomPanelViewModel's IHasBottomPanel parameter:
        _bottomPanelViewModel = new BottomPanelViewModel(buttons, this);
    }

    ...

    public void ExecuteOkCommand(object sender, ExecutedRoutedEventArgs e)
    {
        DialogResult = DialogResult.Ok;
        if (WindowClosing != null) WindowClosing(this, EventArgs.Empty);
    }

    public void CanExecuteOkCommand(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = _messageViewModel.ShowProgressControls
            ? _messageViewModel.ProgressValue == _messageViewModel.MaxProgressValue
            : true;
    }
public static class MsgBox
{
    private static DialogResult MessageBox(MessageViewModel messageViewModel, BottomPanelButtons buttons)
    {
        var viewModel = new MessageWindowViewModel(messageViewModel, buttons);
        var window = new MessageWindow(viewModel);
        window.ShowDialog();
        return viewModel.DialogResult;
    }

    /// <summary>
    /// Displays an informative message to the user.
    /// </summary>
    /// <param name="title">The message's title.</param>
    /// <param name="message">The message's body.</param>
    /// <returns>Returns <see cref="DialogResult.Ok"/> if user closes the window by clicking the Ok button.</returns>
    public static DialogResult Info(string title, string message)
    {
        return Info(title, message, string.Empty);
    }

    /// <summary>
    /// Displays an informative message to the user.
    /// </summary>
    /// <param name="title">The message's title.</param>
    /// <param name="message">The message's body.</param>
    /// <param name="details">The collapsible message's details.</param>
    /// <returns>Returns <see cref="DialogResult.Ok"/> if user closes the window by clicking the Ok button.</returns>
    public static DialogResult Info(string title, string message, string details)
    {
        var viewModel = new MessageViewModel(title, message, details, MessageIcons.Info);
        return MessageBox(viewModel, BottomPanelButtons.Ok);
    }

    /// <summary>
    /// Displays an error message to the user, with stack trace as message details.
    /// </summary>
    /// <param name="title">The message's title.</param>
    /// <param name="exception">The exception to report.</param>
    /// <returns>Returns <see cref="DialogResult.Ok"/> if user closes the window by clicking the Ok button.</returns>
    public static DialogResult Error(string title, Exception exception)
    {
        var viewModel = new MessageViewModel(title, exception.Message, exception.StackTrace, MessageIcons.Error);
        return MessageBox(viewModel, BottomPanelButtons.Ok);
    }
    ...
}