C # обнаруживает, если вызовы были в том же действии пользовательского интерфейса - PullRequest
3 голосов
/ 13 октября 2011

У меня есть приятная работающая функция редактирования-отмены в моем приложении winforms. Он работает с использованием класса CommandStack, который равен двум Stack<IStateCommand> s (один для отмены, один для повторения). Каждая команда имеет метод Execute и Undo, а сам объект CommandStack имеет событие, которое вызывается при изменении стеков.

CommandStack также работает, если метод LogCommand вызывается из его собственной функции Undo и, следовательно, добавляет его в стек повторного выполнения, а не в стек отмены. Это можно сделать, просто добавив текущий ManagingThreadId к объекту List<int>, а затем удалив его после завершения команды отмены (в отличие от использования трассировки стека, которая, я считаю, будет намного медленнее и немного грязной).

В моем приложении много разных команд, так что эта формула как бы задуманна, так как мне потребуется несколько дней, чтобы переделать все эти реализации IStateCommand.

Единственная проблема с этим, в настоящее время некоторые события пользовательского интерфейса также вызывают другие события пользовательского интерфейса, оба из которых записывают IStateCommand в историю отмен. Есть ли способ в C #, который я могу обнаружить, если функция LogCommand уже была вызвана из того же события пользовательского интерфейса (Click, DragDrop, SelectedIndexChanged, TextChanged и т. Д.), Тогда я могу объединить команды в одну команду (используя мой CommandList класс, который также наследует IStateCommand)?

Я думал о том, чтобы сохранить текущее время при вызове события отмены, а затем, если следующая команда будет зарегистрирована менее, чем через x миллисекунд, объединить их в истории, но это выглядит немного неаккуратно. Я также рассмотрел поиск трассировки стека, но я не знаю, что искать, чтобы найти событие корневого интерфейса пользователя, и не знаю, скажу ли я разницу между одним нажатием кнопки, а затем другим щелчком по тому же кнопка.

Также может быть полезно знать, что все эти команды вызываются из потока пользовательского интерфейса из обработчиков событий (в основном из событий из пользовательских элементов управления). Единственная часть моего приложения, которая использует другой поток, запускается после большинства событий пользовательского интерфейса, после того, как журнал отмены зарегистрирован.

Спасибо!

Сортировка

Один и тот же метод вызывается дважды из одного и того же события пользовательского интерфейса (например, MouseUp, DragDrop). Во второй раз, когда вызывается этот метод, как я могу проверить, что он уже был вызван одним и тем же событием интерфейса?

Редактировать: решение (вроде)

Это немного грязно, так как у меня нет времени, чтобы полностью переписать эту систему. Однако я реализовал его таким образом, чтобы в будущем не было такой грязной работы.

Решение основано на одном из комментариев Эрно к его ответу (поэтому я отмечу его ответ как принятый), где он предлагает добавить параметр. Я добавил еще одну перегрузку к своему LogCommand(IStackCommand) методу в классе CommandStack, LogCommand(IStackCommand, string). Строка - это actionId, который сохраняется для каждой команды, и если эта строка совпадает с последней, команды объединяются. Это дает возможность пройти через каждое событие и дать уникальный идентификатор.

Однако, грязная часть - чтобы заставить его работать, прежде чем мы покажем клиенту, actionId по умолчанию System.Windows.Forms.Cursor.Position.ToString(), ой! Поскольку позиция курсора не изменяется во время выполнения потока пользовательского интерфейса, это объединяет каждую команду. На самом деле он даже объединяет команды TextChanged (если они не двигают мышью!)

Ответы [ 3 ]

4 голосов
/ 13 октября 2011

Возможно, можно добавить локальный стек вызываемых команд к команде.

Когда команда выполняет другие команды, добавьте команду в локальный стек, чтобы можно было отменить команды в этом локальном стеке.когда команда должна быть отменена или переделана.

РЕДАКТИРОВАТЬ

Я не совсем уверен, что вы не понимаете.

Я бы просто добавил свойство CommandList в StateCommand.Каждый раз, когда StateCommand вызывает / запускает другую StateCommand, он должен добавить новую StateCommand в CommandList.Таким образом, глобальный CommandList отслеживает команды, которые можно отменить из пользовательского интерфейса, а каждая StateCommand отслеживает вызванные им StateCommands (поэтому они не добавляются в глобальный отмену CommandList)

EDIT 2

Если вы не можете или не хотите переходить к этой настройке, вам нужно будет передать параметр для выполнения команд, связывающих их вместе.

0 голосов
/ 13 октября 2011

Я не знаю, что именно вам нужно для достижения, но я реализовал нечто подобное, может быть, вы можете получить некоторые идеи ...

Таким образом, вы можете хранить некоторую информацию в переменной ThreadStatic . Затем, в любое время, когда вы хотите записать команду, просмотрите статическую переменную потока, чтобы выяснить контекст, в котором вы записываете команду. Если он пуст, вы начинаете новую последовательность регистрации команд. Если нет, вы находитесь внутри последовательности.

Может быть, вы можете сохранить событие ввода (например, Click, DragDrop, ...) или саму команду ... Это зависит от ваших потребностей. Когда начальный обратный вызов события завершен, очистите статическую переменную, чтобы сообщить, что последовательность была завершена.

Я успешно реализовал стратегию, аналогичную командам отслеживания , выполняемым на объектной модели. Я инкапсулировал логику в классе IDisposable, который также реализовал подсчет ссылок для обработки вложенных значений . Первый с использованием запустил последовательность, последующие с использованием операторов увеличили и уменьшили подсчет ссылок, чтобы узнать, когда последовательность была завершена. Внешнее удаление контекста вызвало событие, содержащее все вложенные команды. В моем конкретном случае это сработало отлично, я не знаю, может ли это удовлетворить ваши потребности ...

0 голосов
/ 13 октября 2011

Вы пытались проверить стек методов и проанализировать его по методам:

    StackTrace st = new StackTrace();

    for ( int i=0; i<st.FrameCount; i++ )
{
    StackFrame sf = st.GetFrame(i);
    MethodBase mb = sf.GetMethod();

    // do whatever you want
}   
...