ContentDialog do not subscribe to ICommand.CanExecuteChanged

本文关键字:ICommand CanExecuteChanged to subscribe do not ContentDialog | 更新日期: 2023-09-27 18:21:42

我的Windows.UI.Xaml.Controls.ContentDialog有问题。我想在System.Windows.Input.ICommand.CanExecute返回false时禁用它的主按钮。但ContentDialog似乎没有订阅ICommand.CanExecuteChanged事件。我不知道为什么。这是我的代码:

NewTracingDialog.xaml

<ContentDialog
    x:Class="RouterTracer.NewTracingDialog"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:RouterTracer"
    xmlns:model="using:RouterTracer.ViewModels"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Title="NEW TRACING"
    PrimaryButtonText="trace"  
    SecondaryButtonText="cancel"
    PrimaryButtonCommand="{Binding}"
    d:DataContext="{d:DesignInstance Type=model:NewTracingViewModel}">
    <StackPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
        <TextBox Name="destinationHost" Header="Destination Host" Text="{Binding Host, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        <TextBox Name="port" Header="UDP Port" InputScope="Number" Text="{Binding Port, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        <TextBox Name="hopLimit" Header="Hop Limit" InputScope="Number" Text="{Binding HopLimit, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    </StackPanel>
</ContentDialog>

NewTracingViewModel.cs

//-----------------------------------------------------------------------
// <copyright file="NewTracingViewModel.cs">
//     Copyright (c) Putta Khunchalee.
// </copyright>
// <author>Putta Khunchalee</author>
//-----------------------------------------------------------------------
namespace RouterTracer.ViewModels
{
    using System;
    /// <summary>
    /// View model for new tracing dialog.
    /// </summary>
    public sealed class NewTracingViewModel : ExecutableViewModel
    {
        private byte hopLimit;
        private string host;
        private ushort port;
        /// <summary>
        /// Initialize a new instance of the <see cref="NewTracingViewModel"/> class.
        /// </summary>
        public NewTracingViewModel()
        {
        }
        /// <summary>
        /// Gets or sets TTL or Hop Limit.
        /// </summary>
        /// <value>
        /// TTL or Hop Limit.
        /// </value>
        public byte HopLimit
        {
            get { return this.hopLimit; }
            set { this.SetProperty(ref this.hopLimit, value, "HopLimit"); }
        }
        /// <summary>
        /// Gets or sets destination host.
        /// </summary>
        /// <value>
        /// Hestination host.
        /// </value>
        public string Host
        {
            get { return this.host; }
            set { this.SetProperty(ref this.host, value, "Host"); }
        }
        /// <summary>
        /// Gets or sets destination port.
        /// </summary>
        /// <value>
        /// Destination port.
        /// </value>
        public ushort Port
        {
            get { return this.port; }
            set { this.SetProperty(ref this.port, value, "Port"); }
        }
        /// <summary>
        /// Defines the method to be called when the command is invoked.
        /// </summary>
        /// <param name="parameter">
        /// Data used by the command. If the command does not require data to be passed,
        /// this object can be set to <c>null</c>.
        /// </param>
        public override void Execute(object parameter)
        {
            // This method get execute normally.
        }
        /// <summary>
        /// Validate all properties to determine executable status.
        /// </summary>
        /// <returns>
        /// <c>true</c> if instance can be execute; otherwise <c>false</c>.
        /// </returns>
        protected override bool ValidateProperties()
        {
            if (this.hopLimit == 0)
            {
                return false;
            }
            if (string.IsNullOrWhiteSpace(this.host))
            {
                return false;
            }
            if (this.port == 0)
            {
                return false;
            }
            return true;
        }
    }
}

ExecutableViewModel.cs

//-----------------------------------------------------------------------
// <copyright file="ExecutableViewModel.cs">
//     Copyright (c) Putta Khunchalee.
// </copyright>
// <author>Putta Khunchalee</author>
//-----------------------------------------------------------------------
namespace RouterTracer.ViewModels
{
    using System;
    using System.Windows.Input;
    /// <summary>
    /// Base class for all View Model that executable via <see cref="ICommand"/>.
    /// </summary>
    public abstract class ExecutableViewModel : ViewModel, ICommand
    {
        private bool canExecute;
        /// <summary>
        /// Initialize a new instance of the <see cref="ExecutableViewModel"/> class.
        /// </summary>
        protected ExecutableViewModel()
        {
        }
        /// <summary>
        /// Occurs when changes occur that affect whether or not the command should execute.
        /// </summary>
        public event EventHandler CanExecuteChanged;
        /// <summary>
        /// Defines the method that determines whether the command can execute in its current state.
        /// </summary>
        /// <param name="parameter">
        /// Data used by the command. If the command does not require data to be passed,
        /// this object can be set to <c>null</c>.
        /// </param>
        /// <returns>
        /// <c>true</c> if this command can be executed; otherwise, <c>false</c>.
        /// </returns>
        public bool CanExecute(object parameter)
        {
            return this.canExecute;
        }
        /// <summary>
        /// Defines the method to be called when the command is invoked.
        /// </summary>
        /// <param name="parameter">
        /// Data used by the command. If the command does not require data to be passed,
        /// this object can be set to <c>null</c>.
        /// </param>
        public abstract void Execute(object parameter);
        /// <summary>
        /// Invoked when status of <see cref="CanExecute(object)"/> has changed.
        /// </summary>
        protected virtual void OnCanExecuteChanged()
        {
            var handlers = this.CanExecuteChanged;
            if (handlers != null)
            {
                handlers(this, EventArgs.Empty);
            }
        }
        /// <summary>
        /// Invoked when value of model's property has been changed.
        /// </summary>
        /// <param name="name">
        /// Name of the property that value has changed.
        /// </param>
        protected override void OnPropertyChanged(string name)
        {
            base.OnPropertyChanged(name);
            this.UpdateCanExecuteFlag(ValidateProperties());
        }
        /// <summary>
        /// Validate all properties to determine executable status.
        /// </summary>
        /// <returns>
        /// <c>true</c> if instance can be execute; otherwise <c>false</c>.
        /// </returns>
        protected abstract bool ValidateProperties();
        /// <summary>
        /// Update status of <see cref="CanExecute(object)"/>.
        /// </summary>
        /// <param name="canExecute">
        /// <c>true</c> if instance can be execute; otherwise <c>false</c>.
        /// </param>
        private void UpdateCanExecuteFlag(bool canExecute)
        {
            if (this.canExecute == canExecute)
            {
                return;
            }
            this.canExecute = canExecute;
            this.OnCanExecuteChanged();
        }
    }
}

ViewModel.cs

//-----------------------------------------------------------------------
// <copyright file="ViewModel.cs">
//     Copyright (c) Putta Khunchalee.
// </copyright>
// <author>Putta Khunchalee</author>
//-----------------------------------------------------------------------
namespace RouterTracer.ViewModels
{
    using System.ComponentModel;
    /// <summary>
    /// Base class for all View Model.
    /// </summary>
    public abstract class ViewModel : INotifyPropertyChanged
    {
        /// <summary>
        /// Initialize a new instance of the <see cref="ViewModel"/> class.
        /// </summary>
        protected ViewModel()
        {
        }
        /// <summary>
        /// Raise when value of model's property has been changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>
        /// Invoked when value of model's property has been changed.
        /// </summary>
        /// <param name="name">
        /// Name of the property that value has changed.
        /// </param>
        protected virtual void OnPropertyChanged(string name)
        {
            var handlers = this.PropertyChanged;
            if (handlers != null)
            {
                handlers(this, new PropertyChangedEventArgs(name));
            }
        }
        /// <summary>
        /// Change the value of the field that is property backed.
        /// </summary>
        /// <typeparam name="T">
        /// Type of the field.
        /// </typeparam>
        /// <param name="field">
        /// Field to change value.
        /// </param>
        /// <param name="value">
        /// The new value.
        /// </param>
        /// <param name="name">
        /// Name of backed property.
        /// </param>
        /// <returns>
        /// <c>true</c> when value has been changed; otherwise <c>false</c>.
        /// </returns>
        protected bool SetProperty<T>(ref T field, T value, string name)
        {
            T previousValue;
            // Check to see if value change is neccessary.
            if (object.Equals(field, value))
            {
                return false;
            }
            // Change value.
            previousValue = field;
            field = value;
            this.OnPropertyChanged(name);
            return true;
        }
    }
}

抱歉代码太长。非常感谢。

ContentDialog do not subscribe to ICommand.CanExecuteChanged

我放弃了,改为使用变通方法。也许它还没有在ContentDialog中实现。这是我的变通方法。

NewTracingDialog.xaml.cs

//-----------------------------------------------------------------------
// <copyright file="NewTracingDialog.xaml.cs">
//     Copyright (c) Putta Khunchalee.
// </copyright>
// <author>Putta Khunchalee</author>
//-----------------------------------------------------------------------
namespace RouterTracer
{
    using System;
    using System.Windows.Input;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    /// <summary>
    /// Dialog for gather tracing information from user.
    /// </summary>
    public sealed partial class NewTracingDialog : ContentDialog
    {
        /// <summary>
        /// Used to remove <see cref="OnDataContextCanExecuteChanged(object, EventArgs)"/> before
        /// changing to the new data context.
        /// </summary>
        private ICommand currentDataContext;
        /// <summary>
        /// Initialize a new instance of the <see cref="NewTracingDialog"/> class.
        /// </summary>
        public NewTracingDialog()
        {
            this.InitializeComponent();
            this.DataContextChanged += this.OnDataContextChanged;
        }
        /// <summary>
        /// Invoke when executable flag of data context has changed.
        /// </summary>
        /// <param name="sender">
        /// Data context that executable flag has changed.
        /// </param>
        /// <param name="e">
        /// The empty <see cref="EventArgs"/>.
        /// </param>
        private void OnDataContextCanExecuteChanged(object sender, EventArgs e)
        {
            this.IsPrimaryButtonEnabled = ((ICommand)sender).CanExecute(null);
        }
        /// <summary>
        /// Invoke when data context has been changed.
        /// </summary>
        /// <param name="sender">
        /// The instance of <see cref="NewTracingDialog"/> that data context has changed.
        /// </param>
        /// <param name="args">
        /// Event informations.
        /// </param>
        private void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
        {
            // Get a new data context.
            var command = (ICommand)args.NewValue;
            if (command == null)
            {
                return;
            }
            // Disable primary button if data context is not executable.
            this.IsPrimaryButtonEnabled = command.CanExecute(null);
            // Subscribe to data context executable changed event.
            if (this.currentDataContext != null)
            {
                this.currentDataContext.CanExecuteChanged -= this.OnDataContextCanExecuteChanged;
            }
            command.CanExecuteChanged += this.OnDataContextCanExecuteChanged;
            this.currentDataContext = command;
        }
    }
}