Как часто вы думаете о том, что пользователь может нажать Double-Click, где это не предусмотрено?

    • Silverlight
    • XAML
    • Bug
    • Double Click
  • modified:
  • reading: 3 minutes

Вспомните то время, когда обучали бабушку/дедушку/маму/папу/дядю/тетю основам компьютера. Когда показываете, что на кнопке нужно щелкнуть один раз, на ссылке один раз, но вот на иконке в проводнике два раза (зависит, конечно, от настроек, но все же). Но, если обучаемый научился делать двойной клик, то все. Потом такое ощущение, что он специально тренируясь делает его везде: и на меню, и на кнопке. Бывает, правда, и случайно. В общем, попался недавно неприятный баг. Код в котором баг очевиден видели и дорабатывали несколько раз несколько разных человек (я в их числе), баг не замечали и не обнаруживали. Правда, когда обнаружилась ошибка, я буквально за 10 секунд понял причину, даже не смотря код.

Расскажу на небольшом примере. Пускай у нас есть ViewModel:

public class ViewModel : DependencyObject
{    public static readonly DependencyProperty IsBusyProperty =
        DependencyProperty.Register("IsBusy", typeof(bool), typeof(ViewModel), new PropertyMetadata(false));
     public ViewModel()
    {        ClickMeCommand = new DelegateCommand(OnClickMe);
        ClickCollection = new ObservableCollection<ClickInfoViewModel>();
    }
     public DelegateCommand ClickMeCommand { get; set; }
     public ObservableCollection<ClickInfoViewModel> ClickCollection { get; set; }
     public bool IsBusy
    {        get { return (bool)GetValue(IsBusyProperty); }
        set { SetValue(IsBusyProperty, value); }
    }
     private void OnClickMe(object obj)
    {        IsBusy = true;
        new Thread(DoAsyncAction).Start(DateTime.Now);
    }
     private void DoAsyncAction(object date)
    {
        Thread.Sleep(2000);
        Dispatcher.BeginInvoke(() =>
                                   {                                    ClickCollection.Add(new ClickInfoViewModel((DateTime)date));
                                       IsBusy = false;
                                   });
    }
}

Класс содержит команду ClickMeCommand, которая выполняет, точнее запускает какую-то команду асинхронно. В нашем примере – эта команда DoAsyncAction, которая просто спит 2 секунды, а затем добавляет в коллекцию сделанных кликов новый элемент типа ClickInfoViewModel:

public class ClickInfoViewModel
{    public ClickInfoViewModel(DateTime date)
    {
        Date = date;
    }
     public DateTime Date { get; set; }
     public override string ToString()
    {        return string.Format("Clicked at {0}", Date);
    }
}

Так же ViewModel класс содержит свойство IsBusy, в которое мы выставляем значение, когда наше представление должно быть занято. Уже видите проблему?

Представление будет следующим:

<UserControl x:Class="SilverlightDoubleClick.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:SilverlightDoubleClick="clr-namespace:SilverlightDoubleClick" 
             xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit">
     <UserControl.DataContext>
        <SilverlightDoubleClick:ViewModel />
    </UserControl.DataContext>
     <Grid Width="500" Height="250">
        <StackPanel x:Name="LayoutRoot" Background="White" >
             <Button Command="{Binding Path=ClickMeCommand}">Click Me</Button>
             <ListBox ItemsSource="{Binding Path=ClickCollection}" Height="200" HorizontalAlignment="Stretch" 
                 ScrollViewer.VerticalScrollBarVisibility="Auto"  />
         </StackPanel>
        <Controls:BusyIndicator IsBusy="{Binding IsBusy}"  />
    </Grid>
</UserControl>

Основное на нем – это кнопка, список, отображающий коллекцию сделанных кликов и BusyIndicator, который не дает возможность выполнять действия, пока предыдущее действие не закончится. Собственно можно попробовать. И не забыть попробовать двойной клик:

Get Microsoft Silverlight

Больше чем уверен, что такая ошибка может быть допущена не только мной. В нашем случае автора ошибки уже не найдешь, может быть был и я. У нас это было на форме аутентификации в приложении. Пользователь мог аутентифицироваться дважды. А так как у нас в приложении происходит слежение за сессиями (не давать одному и тому же пользователю работать дважды в одно время), то соответственно после второй аутентификации он видит сообщение о том, что сессия сброшена.

Решить данную проблему очень просто, добавив проверку в метод OnClickMe:

private void OnClickMe(object obj)
{    if (!IsBusy)
    {        IsBusy = true;
        new Thread(DoAsyncAction).Start(DateTime.Now);
    }
}

Было такое? Или я все-таки один такой?

See Also