1

I have problem with disabling controls in WPF Application during processing a function. It is simple app sending data via serial port. When port is "listening" (SerialPort.ReadChar();) I want all controls to go gray/disable them.

This way:

private void startTransmissionButton_Click(object sender, RoutedEventArgs e)
    {
        ComboBox.IsEnabled = false;
        Button1.IsEnabled = false;
        Button2.IsEnabled = false;
        Button3.IsEnabled = false;

        SerialPort com = new SerialPort("COM1");

        com.Open();
        c = (char)com.ReadChar();
        com.Close();

        ComboBox.IsEnabled = true;
        Button1.IsEnabled = true;
        Button2.IsEnabled = true;
        Button3.IsEnabled = true;
    }

disabling seems to work only inside the function, so nothing actually happens in the window. When I remove enabling at the end of function all controls go gray, but not at the moment of *.IsEnabled = false instructions, but when the functions ends. Is there something I do wrong or everything is OK and this needs to be done in different way?

aybe
  • 15,516
  • 9
  • 57
  • 105
Kacper
  • 726
  • 1
  • 6
  • 22
  • If the listening takes time then since the whole code you shared is processed by the same thread, what you are experiencing is normal. To solve it you probably need to do this in a multithreading fashion. There should be a separate thread that controls your UI processing such as updating it s view state. – alainlompo Mar 22 '15 at 19:50
  • Ok, thanks, I understand. Unfortunately I'm beginner in C# and don't know how to properly create threads so if there is any different solution it would be great. – Kacper Mar 22 '15 at 19:53
  • Then this is a great opportunity to learn more about by some practice. Here is a link that may be helpful. http://stackoverflow.com/questions/661561/how-to-update-the-gui-from-another-thread-in-c – alainlompo Mar 22 '15 at 19:57
  • Actually you are right, no need to look for temporary solutions, thanks for help! – Kacper Mar 22 '15 at 20:01

2 Answers2

2

Welcome to StackOverflow !

Since your code is synchronous it is blocking, hence the behavior you get. There is also the need to consider using the Dispatcher but luckily in your case you haven't encountered such issue.

Suggestions:

  • use a ViewModel
  • bind to some properties in it to enable/disable your UI
  • doing so separates concerns and simplifies your thing in general

Example : a 5 second work that disables the UI (really simple !)

enter image description here

Points of interest in my code:

  • by putting all controls that must be disabled within a StackPanel and binding its IsEnabled property the model's IsAvailable property I effectively simplify this process
  • no controls are modified from code-behind
  • the view (your window) does nothing more than presenting, all your logic is in a model that is not tied to your window and can be reused somewhere else

XAML:

<Window x:Class="WpfApplication1.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:wpfApplication1="clr-namespace:WpfApplication1"
        Title="MainView"
        Width="525"
        Height="350"
        d:DataContext="{d:DesignInstance wpfApplication1:MainViewModel,
                                         d:IsDesignTimeCreatable=True}"
        mc:Ignorable="d">
    <Grid>
        <StackPanel>
            <Button Command="{Binding DoSomeWork}" Content="Do some long work" />
            <StackPanel IsEnabled="{Binding IsAvailable}">
                <CheckBox Content="Test control 1" />
                <RadioButton Content="Test control 2" />
            </StackPanel>
            <TextBlock Text="Overall progress:" />
            <ProgressBar Height="10" Value="{Binding CurrentProgress}" />
        </StackPanel>
    </Grid>
</Window>

Code-behind:

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace WpfApplication1
{
    public partial class MainView : Window
    {
        public MainView()
        {
            InitializeComponent();
            DataContext = new MainViewModel();
        }
    }

    // put classes shown below here

}

Your model :

internal class MainViewModel : INotifyPropertyChanged
{
    public MainViewModel()
    {
        // set-up environment
        DoSomeWork = new DelegateCommand(DoSomeWorkExecute, DoSomeWorkCanExecute);
        IsAvailable = true;
    }

    public int CurrentProgress
    {
        get { return _currentProgress; }
        set
        {
            _currentProgress = value;
            OnPropertyChanged();
        }
    }

    #region IsAvailable

    private bool _isAvailable;
    private int _currentProgress;

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

    #endregion

    #region DoSomeWork

    public DelegateCommand DoSomeWork { get; private set; }

    private bool DoSomeWorkCanExecute(object arg)
    {
        return true;
    }

    private async void DoSomeWorkExecute(object o)
    {
        await Task.Run(() =>
        {
            IsAvailable = false;

            var steps = 20;
            var time = 5000;
            var length = time/steps;
            for (var i = 0; i < steps; i++)
            {
                Thread.Sleep(length);
                var currentProgress = (int) (((((double) i + 1)*length)/time)*100);
                CurrentProgress = currentProgress;
            }
            IsAvailable = true;
        });
    }

    #endregion

    #region INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
}

And a trivial command base for DoSomeWork:

internal class DelegateCommand : ICommand
{
    private readonly Func<object, bool> _canExecute;
    private readonly Action<object> _execute;

    public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public DelegateCommand(Action<object> execute)
        : this(execute, s => true)
    {
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    public event EventHandler CanExecuteChanged;
}

TODO

Get familiar with:

You will experience some pain with these concepts the first time, but over time you will find these are the way to go esp. with WPF.

If you are satisfied with my answer, mark it as the answer, otherwise if you need some clarification then add a comment below and either me or someone will try to help further.

aybe
  • 15,516
  • 9
  • 57
  • 105
  • I keep seeing these nice gifs around SO, what tool do you use to make them? +1 for detailed answer. – learningcs Mar 22 '15 at 20:41
  • I use CamStudio to record an AVI, then see http://superuser.com/questions/436056/how-can-i-get-ffmpeg-to-convert-a-mov-to-a-gif to produce a tiny GIF out of it. – aybe Mar 22 '15 at 20:43
1

Please, read the full answer Aybe provided. It's always good to follow best practices. But when it comes to small quick test projects, I believe that sometimes it might be an overkill.

If you need quick solution to this problem then you could try to use the following approach:

private async void startTransmissionButton_Click(object sender, RoutedEventArgs e)
{
    ComboBox.IsEnabled = false;
    Button1.IsEnabled = false;
    Button2.IsEnabled = false;
    Button3.IsEnabled = false;

    await
        Task.Factory.StartNew(
            () =>
            {
                SerialPort com = new SerialPort("COM1");

                com.Open();
                c = (char)com.ReadChar();
                com.Close();
            }
        );

    ComboBox.IsEnabled = true;
    Button1.IsEnabled = true;
    Button2.IsEnabled = true;
    Button3.IsEnabled = true;
}

Note that assigning value to c variable happens in another thread.

I hope my answer is helpful.