Улучшаем Silverlight: окна сообщений
- modified:
- reading: 5 minutes
Любому приложению нужно что-то сообщать пользователю, например, об удачном или неудачном окончании операции, о неожидаемой ошибке в приложении. Реализовать такие информативные области можно по-разному. Есть старый, проверенный, способ с MessageBox. Эти диалоговые окошки с нами уже очень давно, они переходят из технологии в технологию, и в Silverlight есть такой класс MessageBox.
Есть и другой способ отображать сообщения, который я считаю немного дороже по реализации, но который в несколько раз более приятный для глаз пользователя – это отображение этого сообщения не как модального окна, а как некоторой области прикрепленной сверху или снизу. Один из примеров: нотификация в браузерах о том, что страница пытается получить информацию о вашем местоположении. Правильная комбинация обоих способов, конечно же, дает самый лучший и приятный вариант для пользователя, ведь иногда нам все-таки нужно остановить пользователя, чтобы спросить его о чем-то, и тут приходит на помощь первый вариант. Сегодня я хотел бы более подробно обсудить первый вариант на примере Silverlight, так как именно там стандартный MessageBox уж очень скудный.
Первый раз я реализовал свои MessageBox еще для приложения, написанного на Borland C++. Мне показалось, что нужно как-то очень просто и легко улучшить приложение, а так же дать возможность пользователям иметь более простой способ общения со мной (разработчиком), не пытаясь на пальцах объяснить, где и что у него случилось (если речь идет об ошибке), а просто как-то легко сбросить мне лог.
Я разделил окошки по типам. Точнее это, конечно же, сделал не я, а я просто их так понял для себя. Всего их существует 5 типов (иконки, я, как и в прошлый раз, взял из коллекции Visual Studio c:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\VS2010ImageLibrary\):
- Операция завершена. Это первая иконка. Обычно отображается после длительного действия, которое завершилось удачно.
- Информативное окно. Как вариант, может использоваться при нажатии на кнопку About вашего приложения. Более распространенный вариант – отображение сообщения, что действие, которое вы хотите произвести, сейчас невозможно в силу каких-то причин.
- Окно с вопросом. Тут все понятно, нужно запросить у пользователя разрешение на какое-то действие. Стоит ли всегда отображать знак вопроса при отображении окошка с вопросом – вопрос сложный. Например, при удалении объекта, бывает лучше показать важность следующего шага и применить предпоследнюю иконку со знаком вопроса. Иногда, бывает проще отобразить окончание некоторой операции, и сразу же задать вопрос: например, это может быть вопрос “Все сделано. Хотите посмотреть на результат сейчас?”
- Важное сообщение. Я, в большинстве случаев использую его для усиления второго пункта. Для отображения того, что, например, текущий объект нельзя удалить в силу не выданных прав на объект. В общем, обычно, это какая-то ожидаемая ошибка в приложении, но неожидаемая для пользователя ошибка в бизнес процессе.
- Последнюю иконку я использую только в случаях непредвиденных ошибок в приложении. Например, при UnhandledException, либо если работаем с внешними системами (БД, сервисы) для отображения информации о том, что ответ неожиданный или произошла ошибка на той стороне.
В результате, мы имеем следующее перечисление:
public enum MessageBoxIcon
{
Completed = 0,
Error = 1,
Information = 2,
Exclamation = 3,
Question = 4,
}
Теперь по поводу кнопок. В таком окне может быть 4 кнопки: Yes, No, Ok, Cancel. Причем одновременно могут присутствовать только 3. Это комбинации: Ok, Ok|Cancel, Yes|No, Yes|No|Cancel. Более того, вторая и третья комбинация могут заменить друг друга, главное правильно поставить вопрос.
Последняя комбинация Yes|No|Cancel очень важна, старайтесь везде, где это возможно, пользователю, при ответе на вопрос, давать еще возможность отменить операцию. Например, у меня есть приложение, которое генерирует отчеты, нажимаю кнопку “сгенерировать отчет” и приложение меня спрашивает “Вы хотите заменить существующий отчет?”. А я не помню, что там за отчет существует, может я уже действительно его сгенерировал. Хочу посмотреть сначала, а потом уже думать запускать ли эту длительную операцию по генерированию отчета или нет. Но у меня нет выбора, приложение уже запустит этот отчет само, вопрос стоит только в том, куда оно его положит. А потом вы будете ловить непредвиденные ошибки и непредвиденные состояния объектов в вашем приложении, потому что не дали возможность отменить операцию правильно, но пользователь сделал это при помощи кнопки Reset на компьютере.
Вообще, обычно, я делаю перечисление с комбинациями клавиш, которые я описал выше. Но, подумалось, а кто его знает, какие бывают ситуации, и, может быть, бывает необходимо отображать разные комбинации клавиш:
[Flags]public enum MessageBoxButtons
{
Ok = 1,
Yes = 2,
No = 4,
Cancel = 8,
OkCancel = Ok | Cancel,
YesNo = Yes | No,
YesNoCancel = Yes | No | Cancel
}
Не люблю я называть перечисления во множественном числе, но не оставалось выбора, так как MessageBoxButton уже занят самим Silverlight.
Само диалоговое окно может нам вернуть один из 4х результатов, результат нажатия на одну из кнопок в окне:
public enum MessageBoxResult
{
Ok = 0,
Cancel = 1,
Yes = 2,
No = 3
}
А теперь самое главное. Просто отображать окна – это мало. Нужно иметь возможность, особенно из окна с ошибкой, отправлять быстро информацию о месте происхождения ошибки разработчикам или технической поддержки вашего продукта. Я делаю это самым простым способом – добавляю кнопку “Скопировать в буфер обмена”. Вообще, если вы не знали, у стандартных Message Box окон есть возможность скопировать все из окна в буфер обмена, для этого просто держа фокус на этом окне нажать стандартное Ctrl+C, поэтому нельзя лишать этой возможности всех, используя свою реализацию.
Лучший вариант – это иметь сервер, который сразу же будет принимать логи, как, например, это сделано для JetBrains ReSharper EAP версий. Но этот вариант более дорогой.
В общем, вот как у меня выглядят окошки:
Ниже я приведу код, пример того, как я вызываю эти окошки. А так, я скрыл всю реализацию за интерфейсами IMessageService и IMessageWindow. Вот как у меня в результате получается:
private void ShowCompleted(object sender, RoutedEventArgs e)
{ var messageService = _ioC.Resolve<IMessageService>();
messageService.ShowCompleted("Your salary is increased in twice. Press Ok to continue.");
}
private void ShowQuestion(object sender, RoutedEventArgs e)
{ var messageService = _ioC.Resolve<IMessageService>();
messageService.ShowQuestion("Do you want to increased your salary in twice?", (result) =>
{
ResultMessageBox.Text = result.ToString();
});
}
private void ShowInformation(object sender, RoutedEventArgs e)
{ var messageService = _ioC.Resolve<IMessageService>();
messageService.ShowInformation("You are reading outcoldman.ru blog. Thanks for your attention.");
}
private void ShowWarning(object sender, RoutedEventArgs e)
{ var messageService = _ioC.Resolve<IMessageService>();
messageService.ShowExclamation("You can't press this button. Sorry.");
}
private void ShowError(object sender, RoutedEventArgs e)
{ var messageService = _ioC.Resolve<IMessageService>();
try
{ throw new NotImplementedException();
} catch (Exception exception)
{ messageService.ShowError("Unexpected exception.", exception);
}
}
Исходный код можно скачать с assembla.com. У меня там нет никакой MVVM реализации, все сделано по-быстрому.