C# 检测调用是否在同一个 UI 操作中
我的 winforms 应用程序中有一些不错的、有效的编辑撤消功能。它使用 CommandStack 类来工作,该类是两个 Stack
如果从自己的 Undo 函数调用 LogCommand 方法,CommandStack 也会起作用,因此将其添加到重做堆栈,而不是撤消堆栈。只需将当前的 ManagingThreadId
添加到 List
对象,然后在 Undo 命令完成后将其删除即可完成(而不是使用堆栈跟踪,后者我相信会慢得多并且有点脏)。
我的应用程序中有很多不同的命令,因此这个公式有点一成不变,因为我需要几天时间才能重做所有这些 IStateCommand
的实现。
唯一的问题是,目前,其中的一些 UI 事件还会调用其他 UI 事件,这两个事件都会将 IStateCommand
记录到撤消历史记录中。 C# 中是否有任何方法可以检测 LogCommand 函数是否已从同一 UI 事件(Click、DragDrop、SelectedIndexChanged、TextChanged 等)调用,然后我可以将这些命令合并为一个命令(使用我的 CommandList
类,它也继承了IStateCommand
)?
我想过保存调用撤消事件时的当前时间,然后如果在 x 毫秒后记录下一个命令,则将它们合并到历史记录中,但这似乎有点草率。我也考虑过搜索堆栈跟踪,但我真的不知道要寻找什么来查找根 UI 事件,也不知道是否会区分一个按钮单击和同一按钮上的不同单击之间的区别按钮。
了解所有这些命令都是从事件处理程序(主要是来自自定义用户控件的事件)的 UI 线程调用的,这也可能会有所帮助。我的应用程序中唯一使用另一个线程的部分在大多数 UI 事件之后、在记录撤消历史记录之后运行。
谢谢!
排序版本
同一方法从同一 UI 事件(例如,MouseUp、DragDrop)调用两次。第二次调用此方法时,如何检查它是否已被同一个 UI 事件调用过一次?
编辑:解决方案(某种程度上)
这有点脏,因为我没有时间完全重写这个系统。然而,我以这样一种方式实现它,可以选择将来不再那么脏。
该解决方案基于 Erno 对他的答案的评论之一(因此我将他的答案标记为已接受),他建议在其中添加一个参数。我向 CommandStack
类中的 LogCommand(IStackCommand)
方法添加了另一个重载,LogCommand(IStackCommand, string)
。该字符串是actionId,它为每个命令存储,如果该字符串与上一个字符串相同,则命令被合并。这提供了浏览每个事件并提供唯一 ID 的选项。
然而,肮脏的部分 - 为了让它在我们必须向客户端展示之前工作,actionId
默认为System.Windows.Forms.Cursor.Position.ToString ()
,哎呀!!由于 UI 线程执行时光标位置不会更改,因此这会合并每个命令。它实际上甚至结合了 TextChanged 命令(只要它们不移动鼠标!)
I have some nice, working edit-undo functionality in my winforms application. It works using a CommandStack
class, which is two Stack<IStateCommand>
s (one for undo, one for redo). Each command has an Execute
and an Undo
method, and the CommandStack
object itself has an event that is fired when the stacks are changed.
The CommandStack
also works out if the LogCommand method is called from its own Undo function, and therefore adding it to the redo stack, rather than the undo stack. This is done by simply adding the current ManagingThreadId
to a List<int>
object, then removing it after the Undo command is completed (as opposed to using the stack trace, which I believe would be much slower and a bit dirty).
There is a lot of different commands within my application so this formula is sort of set in stone as it'll take me a few days to redo all those IStateCommand
s implementations.
The only problem with this, currently, some UI events within also call other UI events, both of which log an IStateCommand
to the undo history. Is there any way in C# that I can detect if the LogCommand function has already been called from the same UI event (Click, DragDrop, SelectedIndexChanged, TextChanged, etc), then I can combine the commands into one command (using my CommandList
class, which also inherits IStateCommand
)?
I've thought of saving the current time when the undo event was called, then if the next command is logged less than x milliseconds later, combine them in the history, but this seems a bit sloppy. I've also considered searching the stack trace, but I don't really know what to look for to find the root UI event, nor do I know whether I would tell the different between one button click, then a different click on the same button.
It may also be helpful to know that all of these commands are being called from the UI thread from event handlers (mostly from events from custom user controls). The only part of my application that uses another thread runs after most UI events, after the undo history is logged.
Thanks!
Sort Version
The same method is being called twice from the same UI event (eg, MouseUp, DragDrop). The second time this method is called, how do I check that it has already been called once by the same UI event?
Edit: The solution (sort of)
It's a bit of a dirty one as I don't have the time to completely re-write this system. However I've implemented it in such a way that gives the option not to be so dirty in the future.
The solution is based on one of Erno's comments on his answer (so I will mark his answer as accepted), where he suggests added a parameter. I added another overload to my LogCommand(IStackCommand)
method in the CommandStack
class, LogCommand(IStackCommand, string)
. The string is the actionId, which is stored for each command, and if this string is the same as the last, the commands are combined. This gives the option to go through each event and give a unique ID.
However, the dirty part - to get it working before we have to show the client, the actionId
defaults to System.Windows.Forms.Cursor.Position.ToString()
, ouch!! Since the cursor position is not changed while the UI thread is executing, this combines each command. It actually even combines TextChanged commands (as long as they don't move their mouse!)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
可以选择将被调用命令的本地堆栈添加到命令中。
当一个命令执行其他命令时,将该命令添加到本地堆栈,以便在必须撤消或重做该命令时可以撤消此本地堆栈上的命令。
编辑
我不太确定你不明白什么。
我只需将 CommandList 属性添加到 StateCommand 中即可。每次 StateCommand 调用/触发另一个 StateCommand 时,它都应该将新的 StateCommand 添加到 CommandList 中。因此,全局 CommandList 跟踪可以从 UI 撤消的命令,每个 StateCommand 跟踪它调用的 StateCommand(因此这些不会添加到全局撤消 CommandList)
编辑 2
如果可以如果您不想或不想更改该设置,则必须将参数传递给将它们链接在一起的命令的执行。
It might be an option to add a local stack of called-commands to a command.
When a command executes other commands add the command to the local stack so you can undo the commands on this local stack when the command must be undone or redone.
EDIT
I am not quite sure what you don't understand.
I would simply add a CommandList property to the StateCommand. Everytime the StateCommand invokes/triggers another StateCommand it should add the new StateCommand to the CommandList. So the global CommandList keeps track of the Commands that can be undone from the UI and each StateCommand keeps track of the StateCommands it invoked (so these are not added to the global undo CommandList)
EDIT 2
If you can't or do not want to change to that setup you would have to pass a parameter to the execution of the commands that links them together.
您是否尝试检查方法堆栈并逐个方法进行分析:
Did you try to inspect the method stack and analyze it method-by-method:
我不知道你到底需要实现什么,但我实现了类似的东西,也许你可以得到一些想法......
总之,你可以在 ThreadStatic变量。然后,每当您想要记录命令时,请检查线程静态变量以找出记录该命令的上下文。如果它为空,则您正在开始一个新的命令记录序列。如果没有,那么您就处于序列中。
也许您可以存储输入事件(例如单击、拖放...),或命令本身...这取决于您的需要。
当初始事件回调完成时,清除静态变量以表明序列已完成。
我成功地实现了类似的策略来跟踪在对象模型上执行的命令。我将逻辑封装在 IDisposable 类中,该类还实现了引用计数来处理嵌套使用。第一个 using 开始序列,后续的 using 语句增加和减少引用计数以了解序列何时完成。最外面的上下文处理触发了一个包含所有嵌套命令的事件。在我的具体情况下,它运行得很好,我不知道它是否可以满足您的需求......
I don't know what you need exactly to achieve, but I implemented something similar, maybe you can get some ideas...
In summary, you can store some information in a ThreadStatic variable. Then, any time you want to log a command, inspect the thread static variable to find out the context in which you are logging the command. If it's empty, you are starting a new command logging sequence. If not, you are inside a sequence.
Maybe you can store the entry event (e.g. Click, DragDrop,...), or the command itself... It depends on your needs.
When the initial event callback is completed, clean the static variable to signal that the sequence has been completed.
I successfully implemented a similar strategy to track commands executed upon an object model. I encapsulated the logic within an IDisposable class that also implemented the reference counting to handle the nested usings. The first using started the sequence, subsequents using statements increased and decreased the reference counting to know when the sequence was completed. The outermost context disposing fired an event containing all the nested commands. In my specific case it has worked perfectly, I don't know if it may fulfill your needs...