Dockable panel: можно ли блокировать содержимое, если работает команда?

Автор Тема: Dockable panel: можно ли блокировать содержимое, если работает команда?  (Прочитано 2162 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Тема содержит сообщение с Решением. Нажмите здесь чтобы посмотреть его.

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1493
  • Карма: 316
  • Отец modplus.org
Всем привет. Не знаю как точно назвать тему. Сейчас объясню суть проблемы:
Имеется WPF Page, отображаемая в Dockable Panel. Там всего одна кнопка, которая запускает какую-то работу, используя IExternalEventHandler.
Проблема в том, что во время выполнения какой-либо команды (например, простановка размеров) можно перевести мышку на панель и нажать кнопку. Если по кнопке выполняется только IExternalEventHandler, то проблем особо нет - задача ставится в очередь и выполняется после завершения текущей команды. Но вот проблемы возникают, когда до выполнения IExternalEventHandler происходят еще какие-то действия. В моем случае происходит обращение к веб-приложению, потом стартует окно в отдельном потоке (Thread) и только потом запускается IExternalEventHandler.
С точки зрения юзабилити какая-то хрень получается.

Вопрос - как можно поступить в этом случае?

P.S. Вопрос без кода, но думаю имитировать его не сложно. Можно даже взять стандартный пример и по кнопке в панели открыть окно WPF
ModPlus
Блог
Отвечаю в надежде получить плюсики в карму =))

Оффлайн Александр Игнатович

  • Administrator
  • *****
  • Сообщений: 598
  • Карма: 206
  • Skype: alexandr.ignatovich.itc
С точки зрения юзабилити какая-то хрень получается.

Только сейчас, на самом деле, заметил xD

У Тэммика есть статья 2014 года на эту тем - https://thebuildingcoder.typepad.com/blog/2014/03/determining-the-quiescent-state.html

Из неё цитата:
Цитировать
In typical scenarios, end users interact with an external modeless dialogue only when they do not do anything else In Revit at that time. That is why using External events works very well for that kind of applications. The dialogue's controls are enabled all the time except for the time between raising an external event and handling it.

Вообще, хорошего способа, походу и нет (в API так точно с тех пор на эту тему ничего не появилось). Разве что в Idling перезаписывать время вызова, из отдельного thread-а смотреть, сколько времени прошло, но тут будет деградация по производительности, альтернатива можно winAPI посмотреть или, может, читать текущий журнал

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1493
  • Карма: 316
  • Отец modplus.org
Только сейчас, на самом деле, заметил xD
Сам только сегодня узнал об этом. Причем мне сообщили словами "Да это давно уже такое. Мы просто не говорили" =)
Вообще, хорошего способа, походу и нет
Да вот тоже ничего в голову не приходит. Буду думать - может придумаю способ, который не будет затратным
ModPlus
Блог
Отвечаю в надежде получить плюсики в карму =))

Оффлайн Александр Игнатович

  • Administrator
  • *****
  • Сообщений: 598
  • Карма: 206
  • Skype: alexandr.ignatovich.itc
Можно попробовать, например, завести отдельный ExternalEvent с пустым методом Execute, который дергать в разумные интервалы, скажем 0,3-0,5 секунд и проверять результат который возвращает метод ExternalEvent.Raise, если вернулось не Accepted, значит включаем "занят", когда в следующий раз стало Accepted - значит окей.

Оффлайн Andant

  • ADN Club
  • **
  • Сообщений: 79
  • Карма: 26
Булевский статический флажок не поможет?

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1493
  • Карма: 316
  • Отец modplus.org
Булевский статический флажок не поможет?
Поможет, если придумаете как его переключать :)
ModPlus
Блог
Отвечаю в надежде получить плюсики в карму =))

Оффлайн Andant

  • ADN Club
  • **
  • Сообщений: 79
  • Карма: 26
Код - C# [Выбрать]
  1.  public class MyHandler : IExternalEventHandler
  2.     {
  3.         public static bool Process = false;
  4.  
  5.         public string GetName()
  6.         {
  7.             return "MyHandler";
  8.         }
  9.  
  10.         public void Execute(UIApplication uiapp)
  11.         {
  12.             if (Process)
  13.                 return;
  14.             Process = true;
  15.             try
  16.             {
  17.                 ....
  18.             }
  19.             catch
  20.             {
  21.             }
  22.             finally
  23.             {
  24.                 Process = false;
  25.             }
  26.         }
  27.     }
  28.  
« Последнее редактирование: 26-06-2019, 09:46:00 от Александр Ривилис »

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1493
  • Карма: 316
  • Отец modplus.org
Andant, это вы сможете так помечать свои собственные команды, а речь идет вообще о глобальных командах: ваших, других плагинов, Ревита - да неважно.
В автокаде с этим проще - там есть события CommandStarted, CommandEnded (и еще парочка). А вот в Ревите ничего подобного в АПИ не имеется, к сожалению
ModPlus
Блог
Отвечаю в надежде получить плюсики в карму =))

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 10014
  • Карма: 1278
  • Рыцарь ObjectARX
  • Skype: rivilis
Andant,
Вопрос не в том, чтобы не запускать повторно свою команду, если ОНА уже запущена, а не запускать свою команду, если запущена ЛЮБАЯ команда.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Andant

  • ADN Club
  • **
  • Сообщений: 79
  • Карма: 26
Не разобрался в сути вопроса... :-[

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1493
  • Карма: 316
  • Отец modplus.org
Не разобрался в сути вопроса... :-[
Ничего страшного)) Я тоже постоянно не по теме отвечаю  :D :D :D Александр Ривилис может подтвердить =)
ModPlus
Блог
Отвечаю в надежде получить плюсики в карму =))

Отмечено как Решение Александр Пекшев aka Modis 27-06-2019, 11:26:58

Оффлайн Александр Игнатович

  • Administrator
  • *****
  • Сообщений: 598
  • Карма: 206
  • Skype: alexandr.ignatovich.itc
Вот Вам решение, вроде бы всё отлично работает, позже на github выложу полное.

Индикатор занятости приложения:

Код - C# [Выбрать]
  1. public class ApplicationStateIndicator : IExternalEventHandler, IDisposable
  2. {
  3.         private Timer timer;
  4.         private ExternalEvent externalEvent;
  5.        
  6.         public bool IsBusy { get; private set; }
  7.  
  8.         public event EventHandler StateChanged;
  9.  
  10.         public void Initialize(TimeSpan interval)
  11.         {
  12.                 externalEvent = ExternalEvent.Create(this);
  13.  
  14.                 externalEvent.Raise();
  15.  
  16.                 timer = new Timer(Callback, null, TimeSpan.Zero, interval);
  17.         }
  18.  
  19.         public void Execute(UIApplication app)
  20.         {
  21.                
  22.         }
  23.  
  24.         public string GetName() => nameof(ApplicationStateIndicator);
  25.  
  26.         private void Callback(object state)
  27.         {
  28.                 var pendingState = externalEvent.IsPending;
  29.  
  30.                 if (pendingState != IsBusy)
  31.                         StateChanged?.Invoke(this, EventArgs.Empty);
  32.  
  33.                 IsBusy = externalEvent.IsPending;
  34.  
  35.                 externalEvent.Raise();
  36.         }
  37.  
  38.         public void Dispose() => timer.Dispose();
  39. }

Ну и моделька mvvm:
Код - C# [Выбрать]
  1. public class Model : ViewModelBase
  2. {
  3.         private static readonly Lazy<Model> InstanceObj = new Lazy<Model>(() => new Model());
  4.  
  5.         private Model()
  6.         {
  7.                 var stateIndicator = new ApplicationStateIndicator();
  8.  
  9.                 stateIndicator.StateChanged += (sender, args) =>
  10.                         {
  11.                                 IsAvailable = stateIndicator.IsBusy;
  12.  
  13.                                 RaisePropertyChanged(() => IsAvailable);
  14.                         };
  15.  
  16.                 stateIndicator.Initialize(TimeSpan.FromSeconds(0.5));
  17.         }
  18.  
  19.         public static Model Instance => InstanceObj.Value;
  20.  
  21.         public bool IsAvailable { get; private set; }
  22. }

Тут есть какая-то несостыковка, по-идее, должно быть IsAvailable = !stateIndicator.IsBusy; Но код рабочий. Разберусь попозже, как время будет

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1493
  • Карма: 316
  • Отец modplus.org
Александр Игнатович, супер. Добавил в свой проект, тестирую - все работает как надо
ModPlus
Блог
Отвечаю в надежде получить плюсики в карму =))

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1493
  • Карма: 316
  • Отец modplus.org
Забиндил результат проверки к свойству IsEnabled кнопки в панели... Сейчас наблюдаю интересную картину - кнопка мигает постоянно. Получается, что фоном выполняется какая-то работа с примерно таким-же промежутком времени?
ModPlus
Блог
Отвечаю в надежде получить плюсики в карму =))

Оффлайн Александр Игнатович

  • Administrator
  • *****
  • Сообщений: 598
  • Карма: 206
  • Skype: alexandr.ignatovich.itc
Я, честно говоря, накидал по-быстрому решение, у меня, вроде бы, всё норм, также забиндил на IsEnabled кнопки, вроде морганий не наблюдал.

Вообще, думаю, нужно поправить здесь:
Код - C# [Выбрать]
  1.                 IsBusy = externalEvent.IsPending;
  2.  
  3.                 externalEvent.Raise();

Вызывать Raise, если только IsPending false.