Question

I am new in MVVM and I have a strange behavior that I haven't succeed to resolve: I have some buttons (and other elements) displayed via a list:

My application GUI

Even if the other elements behave correctly to data-binding (updating when the objects changes their states), the buttons only deactivates correctly but do not reactivate in relation to the command state: I must click on the GUI to refresh and get the updated and correct state.

I found on StackOverflow that this issue could be corrected by using:

CommandManager.InvalidateRequerySuggested();

But I didn't succeed to find how to use it: or this doesn't have any impact, or (when place in my RelayCommand - and that doesn't see to be a good idea anyway) it gives the good behavior to the buttons but makes the other items behave incorrectly.

Please find my XAML:

<DataTemplate x:Key="ProjectTemplate">
     <StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" Margin="0, 2, 0, 2">
          <ProgressBar Value="{Binding BuildProgress}" Width="60" Height="15"/>
          <TextBox Text="{Binding Label}" MinWidth="120" IsEnabled="{Binding IsLabelAvailable}" Margin="5,0,0,0" />
          <CheckBox Content="Archive" IsChecked="{Binding ToBeArchived}" IsEnabled="{Binding IsAvailable}" Margin="5,4,0,0" />
          <Button Content="Build" Command="{Binding Path=BuildCommand}" Margin="5,0,0,0" />
          <Button Content="Rebuild" Command="{Binding Path=RebuildCommand}" Margin="5,0,0,0" />
          <Button Content="Publish" Command="{Binding Path=PublishCommand}" Margin="5,0,0,0" />
          <TextBlock Text="{Binding Status}" Margin="10,0,0,0" />
     </StackPanel>
</DataTemplate>
<ListBox Grid.Row="0" ItemsSource="{Binding Path=Projects}" ItemTemplate="{StaticResource ProjectTemplate}" >
        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Style.Triggers>
                    <Trigger Property="IsSelected" Value="True" >
                        <Setter Property="FontWeight" Value="Normal" />
                        <Setter Property="Background" Value="Transparent" />
                        <Setter Property="Foreground" Value="Black" />
                    </Trigger>
                </Style.Triggers>
                <Style.Resources>
                    <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/>
                </Style.Resources>
            </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

My ViewModel:

    public ObservableCollection<Project> Projects
    {
        get
        {
            return _projects;
        }

        set
        {
            if (_projects == value)
                return;

            _projects = value;
            OnPropertyChanged("Projects");
        }
    }

And my Model:

    private readonly Lazy<ICommand> _lazyRebuildCommand;
    private bool _isAvailable;

    public Project()
    {
        IsAvailable = true;
        _lazyRebuildCommand = new Lazy<ICommand>(() =>
            new RelayCommand(
                param => BuildProject(true),
                param => IsAvailable
                ));
    }

    public ICommand RebuildCommand
    {
        get
        {
            return _lazyRebuildCommand.Value;
        }
    }

    public bool IsAvailable
    {
        get
        {
            return _isAvailable;
        }
        set
        {
            _isAvailable = value;
            OnPropertyChanged("IsAvailable");
        }
    }

Thanks for any help!

EDIT: Here is the process where the model is used: I'm using a Task to handle a Queue in which I add the projects I want to process:

private static readonly Queue<Project> ProjectsToBuild = new Queue<Project>();
private static bool _isInitialized = false;

public static void AddProjectToBuild(Project projectToAdd)
    {
        projectToAdd.IsAvailable = false;
        ProjectsToBuild.Enqueue(projectToAdd);

        if (!_isInitialized)
        {
            Task.Factory.StartNew(() => ProcessQueue());
            _isInitialized = true;
        }
    }

private static void ProcessQueue()
    {
        while (true)
        {
            if (ProjectsToBuild.Count > 0)
            {
                var project = ProjectsToBuild.Dequeue();
                ProcessCurrentProject(project);                    
            }
            Thread.Sleep(200);
        }
    }

private static void ProcessCurrentProject(Project project)
    {
        Thread.Sleep(3000);
        project.BuildProgress = 50;
        Thread.Sleep(3000);
        project.BuildProgress = 100;
        project.IsPublishable = true;
        project.IsAvailable = true;
        project.RaiseProjectProcessedEvent();
        return;
    }

EDIT2: The RelayCommand I use:

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)
    {
        //CommandManager.InvalidateRequerySuggested();
        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
}
Was it helpful?

Solution 2

I finally solve this issue in a way that anyone having this problem will be able to use easily.

In the .xaml.cs file of your form, subscribe to the Rendering event to shoot the "InvalidateRequerySuggested" that will reactivate the buttons:

    public MainWindow()
    {
        InitializeComponent();
        CompositionTarget.Rendering += OnRendering;
    }

    void OnRendering(object sender, EventArgs e)
    {
        Application.Current.Dispatcher.BeginInvoke(
           DispatcherPriority.Background,
           new Action(CommandManager.InvalidateRequerySuggested));
    }

It's code behind, but as it's for resolving a GUI only issue, I think it's correct.

OTHER TIPS

I believe its a problem with the template finding the right status.

Take out the guesswork due to the template and bind the IsEnable directly to an associated Can.. status property (I can't divine if you have those properties currently and you probably need to add them) but something like this:

<DataTemplate x:Key="ProjectTemplate">
<StackPanel Orientation="Horizontal">
   <ProgressBar Value="{Binding BuildProgress}" />
   <TextBox Text="{Binding Label}" IsEnabled="{Binding IsLabelAvailable}" />
   <CheckBox Content="Archive" IsChecked="{Binding ToBeArchived}" IsEnabled="{Binding IsAvailable}" />
   <Button Content="Build" 
           Command="{Binding Path=BuildCommand}" 
           IsEnabled="{Binding CanBuild}"  />
   <Button Content="Rebuild" 
           Command="{Binding Path=RebuildCommand}" 
           IsEnabled="{Binding CanReBuild}" />
   <Button Content="Publish" 
           Command="{Binding Path=PublishCommand}" 
           IsEnabled="{Binding CanPublish}" />
   <TextBlock Text="{Binding Status}" />
   </StackPanel>
</DataTemplate>

Then make sure the Can... properties adhere to INotifyProperty change of course.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top