Skip to content

Commit 6281778

Browse files
committed
Улучшено управление жизненным циклом хоткеев, README
Добавлен HashSet для отслеживания привязок в GlobalHotKeysCollection и методы для корректного управления жизненным циклом GlobalHotKeyBinding при изменениях коллекции. Реализовано удаление идентификаторов хоткеев из внутренней таблицы при их снятии регистрации. Добавлена очистка обработчика сообщений при отсутствии хоткеев. В проект добавлен README.MD с примерами использования и описанием компонентов.
1 parent 4791278 commit 6281778

2 files changed

Lines changed: 127 additions & 8 deletions

File tree

MathCore.WPF/AttachedProperties/HotKeys/GlobalHotKeysCollection.cs

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ internal static (Keys Key, ModifierKeys Modifer) GetModifiers(Keys Key, Modifier
4545
return (key, modifers);
4646
}
4747

48+
private readonly HashSet<GlobalHotKeyBinding> _Bindings = [];
49+
4850
/// <summary>Инициализация новой коллекции горячих клавиш</summary>
4951
public GlobalHotKeysCollection() => ((INotifyCollectionChanged)this).CollectionChanged += OnCollectionChanged;
5052

@@ -57,22 +59,62 @@ private void OnCollectionChanged(object? Sender, NotifyCollectionChangedEventArg
5759
switch (e.Action)
5860
{
5961
case NotifyCollectionChangedAction.Add:
60-
if (e.NewItems?.Cast<GlobalHotKeyBinding>() is { } added)
61-
foreach (var item in e.NewItems.Cast<GlobalHotKeyBinding>()) item.SetHost(this);
62+
AddBindings(e.NewItems);
6263
break;
6364
case NotifyCollectionChangedAction.Remove:
64-
if (e.OldItems?.Cast<GlobalHotKeyBinding>() is { } removed)
65-
foreach (var item in removed) item.SetHost(null);
65+
RemoveBindings(e.OldItems);
6666
break;
6767
case NotifyCollectionChangedAction.Replace:
68-
if (e.NewItems?.Cast<GlobalHotKeyBinding>() is { } @new)
69-
foreach (var item in @new) item.SetHost(this);
70-
if (e.OldItems?.Cast<GlobalHotKeyBinding>() is { } old)
71-
foreach (var item in old) item.SetHost(null);
68+
AddBindings(e.NewItems);
69+
RemoveBindings(e.OldItems);
70+
break;
71+
case NotifyCollectionChangedAction.Reset:
72+
ResetBindings();
7273
break;
7374
}
7475
}
7576

77+
private void AddBindings(IList? Items)
78+
{
79+
if (Items is null) return;
80+
81+
foreach (GlobalHotKeyBinding item in Items)
82+
{
83+
item.SetHost(this);
84+
_Bindings.Add(item);
85+
}
86+
}
87+
88+
private void RemoveBindings(IList? Items)
89+
{
90+
if (Items is null) return;
91+
92+
foreach (GlobalHotKeyBinding item in Items)
93+
{
94+
item.SetHost(null);
95+
_Bindings.Remove(item);
96+
}
97+
}
98+
99+
private void ResetBindings()
100+
{
101+
var current_bindings = new HashSet<GlobalHotKeyBinding>(this);
102+
103+
foreach (var binding in _Bindings)
104+
{
105+
if (current_bindings.Contains(binding)) continue;
106+
107+
binding.SetHost(null);
108+
Unregister(binding);
109+
}
110+
111+
foreach (var binding in current_bindings)
112+
binding.SetHost(this);
113+
114+
_Bindings.Clear();
115+
_Bindings.UnionWith(current_bindings);
116+
}
117+
76118
protected override Freezable CreateInstanceCore()
77119
{
78120
var collection = new GlobalHotKeysCollection();
@@ -131,6 +173,24 @@ private static ushort GetHotKeyId((Keys Key, ModifierKeys Modifer) Key)
131173
return key_id;
132174
}
133175

176+
private static void RemoveHotKeyId(ushort KeyId)
177+
{
178+
(Keys Key, ModifierKeys Modifer) key_to_remove = default;
179+
var has_key = false;
180+
181+
foreach (var pair in __HotKeyIds)
182+
{
183+
if (pair.Value != KeyId) continue;
184+
185+
key_to_remove = pair.Key;
186+
has_key = true;
187+
break;
188+
}
189+
190+
if (has_key)
191+
__HotKeyIds.Remove(key_to_remove);
192+
}
193+
134194
/// <summary>Таблица идентификаторов зарегистрированных горячих клавиш</summary>
135195
private static readonly HashSet<ushort> __RegisteredHotKeys = [];
136196

@@ -190,8 +250,15 @@ private static void UnregisterHotKey(ushort KeyId)
190250
User32.UnregisterHotKey(IntPtr.Zero, KeyId);
191251
__RegisteredHotKeys.Remove(KeyId);
192252

253+
RemoveHotKeyId(KeyId);
193254
Kernel32.GlobalDeleteAtom(KeyId);
194255

256+
if (__HotKeyBindings is { Count: 0 })
257+
{
258+
ComponentDispatcher.ThreadPreprocessMessage -= OnFilterMessage;
259+
__HotKeyBindings = null;
260+
}
261+
195262
Debug.WriteLine("Hot key {0} unregistered at system");
196263
}
197264

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# GlobalHotKeys
2+
3+
Компоненты для регистрации глобальных горячих клавиш в WPF с привязкой к `ICommand`.
4+
5+
## Состав
6+
7+
- `GlobalHotKeysCollection` — коллекция горячих клавиш, подключаемая через attached‑свойство `UI.HotKeys`
8+
- `GlobalHotKeyBinding` — описание одной горячей клавиши (клавиша, модификаторы, команда, параметр)
9+
10+
## Использование в XAML
11+
12+
```xml
13+
<Window
14+
xmlns:local="clr-namespace:MathCore.WPF">
15+
<local:UI.HotKeys>
16+
<local:GlobalHotKeysCollection>
17+
<local:GlobalHotKeyBinding
18+
Key="F5"
19+
Command="{Binding RefreshCommand}" />
20+
<local:GlobalHotKeyBinding
21+
Key="S"
22+
Modifer="Control"
23+
Command="{Binding SaveCommand}" />
24+
</local:GlobalHotKeysCollection>
25+
</local:UI.HotKeys>
26+
</Window>
27+
```
28+
29+
## Использование в коде
30+
31+
```csharp
32+
var hot_keys = UI.GetHotKeys(this);
33+
34+
hot_keys.Add(new GlobalHotKeyBinding
35+
{
36+
Key = Keys.F5,
37+
Command = RefreshCommand
38+
});
39+
40+
hot_keys.Add(new GlobalHotKeyBinding
41+
{
42+
Key = Keys.S,
43+
Modifer = ModifierKeys.Control,
44+
Command = SaveCommand,
45+
CommandParameter = "autosave"
46+
});
47+
```
48+
49+
## Примечания
50+
51+
- Коллекция автоматически освобождает зарегистрированные хоткеи при выгрузке `Window`
52+
- Горячие клавиши обрабатываются на уровне системных сообщений, поэтому работают вне фокуса элементов управления

0 commit comments

Comments
 (0)