ShellSubMenuDialog.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. using BluePointLilac.Controls;
  2. using BluePointLilac.Methods;
  3. using ContextMenuManager.Controls.Interfaces;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Drawing;
  7. using System.IO;
  8. using System.Linq;
  9. using System.Reflection;
  10. using System.Windows.Forms;
  11. using static Microsoft.Win32.Registry;
  12. namespace ContextMenuManager.Controls
  13. {
  14. sealed class ShellSubMenuDialog : CommonDialog
  15. {
  16. public Icon Icon { get; set; }
  17. public string Text { get; set; }
  18. public override void Reset() { }
  19. protected override bool RunDialog(IntPtr hwndOwner) { return false; }
  20. /// <param name="parentPath">子菜单的父菜单的注册表路径</param>
  21. public void ShowDialog(string parentPath)
  22. {
  23. using(ShellSubMenuForm frm = new ShellSubMenuForm())
  24. {
  25. frm.Text = this.Text;
  26. frm.Icon = this.Icon;
  27. frm.ParentPath = parentPath;
  28. frm.ShowDialog();
  29. }
  30. }
  31. sealed class ShellSubMenuForm : Form
  32. {
  33. public ShellSubMenuForm()
  34. {
  35. this.StartPosition = FormStartPosition.CenterParent;
  36. this.ShowInTaskbar = this.MaximizeBox = this.MinimizeBox = false;
  37. this.MinimumSize = this.Size = new Size(646, 369).DpiZoom();
  38. this.Controls.AddRange(new Control[] { MlbSubItems, StatusBar });
  39. StatusBar.CanMoveForm();
  40. this.OnResize(null);
  41. }
  42. /// <summary>子菜单的父菜单的注册表路径</summary>
  43. public string ParentPath { get; set; }
  44. readonly MyListBox MlbSubItems = new MyListBox { Dock = DockStyle.Fill };
  45. readonly MyStatusBar StatusBar = new MyStatusBar();
  46. private MyList LstSubItems;
  47. protected override void OnLoad(EventArgs e)
  48. {
  49. base.OnLoad(e);
  50. bool isPublic = true;
  51. string value = GetValue(ParentPath, "SubCommands", null)?.ToString();
  52. if(value == null) isPublic = false;
  53. else if(value.IsNullOrWhiteSpace())
  54. {
  55. using(var shellKey = RegistryEx.GetRegistryKey($@"{ParentPath}\shell"))
  56. {
  57. if(shellKey != null && shellKey.GetSubKeyNames().Length > 0) isPublic = false;
  58. else
  59. {
  60. using(SubMenuModeForm frm = new SubMenuModeForm())
  61. {
  62. frm.ShowDialog();
  63. switch(frm.Mode)
  64. {
  65. case SubMenuModeForm.SubMode.Public:
  66. isPublic = true; break;
  67. case SubMenuModeForm.SubMode.Private:
  68. isPublic = false; break;
  69. case SubMenuModeForm.SubMode.None:
  70. this.Dispose(); return;
  71. }
  72. }
  73. }
  74. }
  75. }
  76. if(isPublic)
  77. {
  78. LstSubItems = new PulicMultiItemsList(MlbSubItems);
  79. ((PulicMultiItemsList)LstSubItems).LoadItems(ParentPath);
  80. this.Text += $"({AppString.Dialog.Public})";
  81. }
  82. else
  83. {
  84. LstSubItems = new PrivateMultiItemsList(MlbSubItems);
  85. ((PrivateMultiItemsList)LstSubItems).LoadItems(ParentPath);
  86. this.Text += $"({AppString.Dialog.Private})";
  87. }
  88. LstSubItems.HoveredItemChanged += (sender, a) =>
  89. {
  90. if(!AppConfig.ShowFilePath) return;
  91. MyListItem item = LstSubItems.HoveredItem;
  92. if(item is ITsiFilePathItem pathItem)
  93. {
  94. string path = pathItem.ItemFilePath;
  95. if(File.Exists(path)) { StatusBar.Text = path; return; }
  96. }
  97. StatusBar.Text = item.Text;
  98. };
  99. }
  100. sealed class SubMenuModeForm : Form
  101. {
  102. public SubMenuModeForm()
  103. {
  104. this.Text = AppString.General.AppName;
  105. this.ShowIcon = this.ShowInTaskbar = false;
  106. this.MinimizeBox = this.MaximizeBox = false;
  107. this.FormBorderStyle = FormBorderStyle.FixedSingle;
  108. this.StartPosition = FormStartPosition.CenterParent;
  109. this.Font = new Font(SystemFonts.MessageBoxFont.FontFamily, 9F);
  110. this.Controls.AddRange(new Control[] { pnlTop, btnPrivate, btnPublic });
  111. pnlTop.Controls.Add(lblInfo);
  112. int a = 20.DpiZoom();
  113. this.ClientSize = new Size(lblInfo.Width + 2 * a, lblInfo.Height + btnPrivate.Height + 3 * a);
  114. lblInfo.Location = new Point(a, a);
  115. pnlTop.Height = lblInfo.Bottom + a;
  116. btnPrivate.Top = btnPublic.Top = pnlTop.Bottom + a / 2;
  117. btnPublic.Left = pnlTop.Width - btnPublic.Width - a;
  118. btnPrivate.Left = btnPublic.Left - btnPrivate.Width - a;
  119. btnPrivate.Click += (sender, e) => Mode = SubMode.Private;
  120. btnPublic.Click += (sender, e) => Mode = SubMode.Public;
  121. }
  122. public enum SubMode { Public, Private, None }
  123. public SubMode Mode { get; private set; } = SubMode.None;
  124. readonly Label lblInfo = new Label
  125. {
  126. Text = AppString.Dialog.SelectSubMenuMode,
  127. AutoSize = true
  128. };
  129. readonly Panel pnlTop = new Panel
  130. {
  131. BackColor = Color.White,
  132. Dock = DockStyle.Top
  133. };
  134. readonly Button btnPrivate = new Button
  135. {
  136. Text = AppString.Dialog.Private,
  137. DialogResult = DialogResult.OK,
  138. AutoSize = true
  139. };
  140. readonly Button btnPublic = new Button
  141. {
  142. Text = AppString.Dialog.Public,
  143. DialogResult = DialogResult.OK,
  144. AutoSize = true
  145. };
  146. }
  147. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
  148. sealed class PulicMultiItemsList : MyList
  149. {
  150. readonly List<string> SubKeyNames = new List<string>();
  151. /// <summary>子菜单的父菜单的注册表路径</summary>
  152. private string ParentPath { get; set; }
  153. /// <summary>菜单所处环境注册表路径</summary>
  154. private string ScenePath => RegistryEx.GetParentPath(RegistryEx.GetParentPath(ParentPath));
  155. readonly SubNewItem subNewItem = new SubNewItem(true);
  156. public PulicMultiItemsList(MyListBox owner) : base(owner)
  157. {
  158. this.AddItem(subNewItem);
  159. subNewItem.AddNewItem += (sender, e) => AddNewItem();
  160. subNewItem.AddExisting += (sender, e) => AddReference();
  161. subNewItem.AddSeparator += (sender, e) => AddSeparator();
  162. }
  163. /// <param name="parentPath">子菜单的父菜单的注册表路径</param>
  164. public void LoadItems(string parentPath)
  165. {
  166. this.ParentPath = parentPath;
  167. string value = GetValue(ParentPath, "SubCommands", null)?.ToString();
  168. Array.ForEach(value.Split(';'), cmd => SubKeyNames.Add(cmd.TrimStart()));
  169. SubKeyNames.RemoveAll(string.IsNullOrEmpty);
  170. using(var shellKey = RegistryEx.GetRegistryKey(ShellItem.CommandStorePath, false, true))
  171. {
  172. foreach(string keyName in SubKeyNames)
  173. {
  174. using(var key = shellKey.OpenSubKey(keyName))
  175. {
  176. MyListItem item;
  177. if(key != null) item = new SubShellItem(this, keyName);
  178. else if(keyName == "|") item = new SeparatorItem(this);
  179. else item = new InvalidItem(this, keyName);
  180. this.AddItem(item);
  181. }
  182. }
  183. }
  184. }
  185. private void AddNewItem()
  186. {
  187. if(!SubShellTypeItem.CanAddMore(this)) return;
  188. using(NewShellDialog dlg = new NewShellDialog
  189. {
  190. ScenePath = this.ScenePath,
  191. ShellPath = ShellItem.CommandStorePath
  192. })
  193. {
  194. if(dlg.ShowDialog() != DialogResult.OK) return;
  195. SubKeyNames.Add(dlg.NewItemKeyName);
  196. SaveSorting();
  197. this.AddItem(new SubShellItem(this, dlg.NewItemKeyName));
  198. }
  199. }
  200. private void AddReference()
  201. {
  202. if(!SubShellTypeItem.CanAddMore(this)) return;
  203. using(ShellStoreDialog dlg = new ShellStoreDialog
  204. {
  205. ShellPath = ShellItem.CommandStorePath,
  206. IgnoredKeyNames = ShellItem.SysStoreItemNames.ToList()
  207. })
  208. {
  209. if(dlg.ShowDialog() != DialogResult.OK) return;
  210. dlg.SelectedKeyNames.ForEach(keyName =>
  211. {
  212. if(!SubShellTypeItem.CanAddMore(this)) return;
  213. this.AddItem(new SubShellItem(this, keyName));
  214. this.SubKeyNames.Add(keyName);
  215. SaveSorting();
  216. });
  217. }
  218. }
  219. private void AddSeparator()
  220. {
  221. this.SubKeyNames.Add("|");
  222. SaveSorting();
  223. this.AddItem(new SeparatorItem(this));
  224. }
  225. private void SaveSorting()
  226. {
  227. SetValue(ParentPath, "SubCommands", string.Join(";", SubKeyNames.ToArray()));
  228. }
  229. private void MoveItem(MyListItem item, bool isUp)
  230. {
  231. int index = this.GetItemIndex(item);
  232. if(isUp)
  233. {
  234. if(index > 1)
  235. {
  236. this.SetItemIndex(item, index - 1);
  237. this.SubKeyNames.Reverse(index - 2, 2);
  238. }
  239. }
  240. else
  241. {
  242. if(index < this.Controls.Count - 1)
  243. {
  244. this.SetItemIndex(item, index + 1);
  245. this.SubKeyNames.Reverse(index - 1, 2);
  246. }
  247. }
  248. this.SaveSorting();
  249. }
  250. private void DeleteItem(MyListItem item)
  251. {
  252. int index = this.GetItemIndex(item);
  253. this.Controls.Remove(item);
  254. this.Controls[index - 1].Focus();
  255. this.SubKeyNames.RemoveAt(index - 1);
  256. this.SaveSorting();
  257. item.Dispose();
  258. }
  259. sealed class SubShellItem : SubShellTypeItem
  260. {
  261. public SubShellItem(PulicMultiItemsList list, string keyName) : base($@"{CommandStorePath}\{keyName}")
  262. {
  263. this.Owner = list;
  264. BtnMoveUp.MouseDown += (sender, e) => Owner.MoveItem(this, true);
  265. BtnMoveDown.MouseDown += (sender, e) => Owner.MoveItem(this, false);
  266. ContextMenuStrip.Items.Remove(TsiDeleteMe);
  267. ContextMenuStrip.Items.Add(TsiDeleteRef);
  268. TsiDeleteRef.Click += (sender, e) => DeleteReference();
  269. }
  270. readonly ToolStripMenuItem TsiDeleteRef = new ToolStripMenuItem(AppString.Menu.DeleteReference);
  271. public PulicMultiItemsList Owner { get; private set; }
  272. private void DeleteReference()
  273. {
  274. if(MessageBoxEx.Show(AppString.MessageBox.ConfirmDeleteReference,
  275. MessageBoxButtons.YesNo) == DialogResult.Yes)
  276. {
  277. Owner.DeleteItem(this);
  278. }
  279. }
  280. }
  281. sealed class SeparatorItem : SubSeparatorItem
  282. {
  283. public SeparatorItem(PulicMultiItemsList list) : base()
  284. {
  285. this.Owner = list;
  286. BtnMoveUp.MouseDown += (sender, e) => Owner.MoveItem(this, true);
  287. BtnMoveDown.MouseDown += (sender, e) => Owner.MoveItem(this, false);
  288. }
  289. public PulicMultiItemsList Owner { get; private set; }
  290. public override void DeleteMe()
  291. {
  292. Owner.DeleteItem(this);
  293. }
  294. }
  295. sealed class InvalidItem : MyListItem, IBtnDeleteItem, IBtnMoveUpDownItem
  296. {
  297. public InvalidItem(PulicMultiItemsList list, string keyName)
  298. {
  299. this.Owner = list;
  300. this.Text = $"{AppString.Other.InvalidItem} {keyName}";
  301. this.Image = AppImage.NotFound.ToTransparent();
  302. BtnDelete = new DeleteButton(this);
  303. BtnMoveDown = new MoveButton(this, false);
  304. BtnMoveUp = new MoveButton(this, true);
  305. BtnMoveUp.MouseDown += (sender, e) => Owner.MoveItem(this, true);
  306. BtnMoveDown.MouseDown += (sender, e) => Owner.MoveItem(this, false);
  307. MyToolTip.SetToolTip(this, AppString.Tip.InvalidItem);
  308. MyToolTip.SetToolTip(BtnDelete, AppString.Menu.Delete);
  309. }
  310. public DeleteButton BtnDelete { get; set; }
  311. public PulicMultiItemsList Owner { get; private set; }
  312. public MoveButton BtnMoveUp { get; set; }
  313. public MoveButton BtnMoveDown { get; set; }
  314. public void DeleteMe()
  315. {
  316. Owner.DeleteItem(this);
  317. }
  318. }
  319. }
  320. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
  321. sealed class PrivateMultiItemsList : MyList
  322. {
  323. public PrivateMultiItemsList(MyListBox owner) : base(owner)
  324. {
  325. this.AddItem(subNewItem);
  326. subNewItem.AddNewItem += (sender, e) => AddNewItem();
  327. subNewItem.AddSeparator += (sender, e) => AddSeparator();
  328. subNewItem.AddExisting += (sender, e) => AddFromParentMenu();
  329. }
  330. readonly SubNewItem subNewItem = new SubNewItem(false);
  331. /// <summary>父菜单的注册表路径</summary>
  332. public string ParentPath { get; set; }
  333. /// <summary>子菜单的Shell项注册表路径</summary>
  334. private string ShellPath { get; set; }
  335. /// <summary>父菜单的Shell项注册表路径</summary>
  336. private string ParentShellPath => RegistryEx.GetParentPath(ParentPath);
  337. /// <summary>菜单所处环境注册表路径</summary>
  338. private string ScenePath => RegistryEx.GetParentPath(ParentShellPath);
  339. /// <summary>父菜单的项名</summary>
  340. private string ParentKeyName => RegistryEx.GetKeyName(ParentPath);
  341. public void LoadItems(string parentPath)
  342. {
  343. this.ParentPath = parentPath;
  344. string sckValue = GetValue(parentPath, "ExtendedSubCommandsKey", null)?.ToString();
  345. if(!sckValue.IsNullOrWhiteSpace())
  346. {
  347. this.ShellPath = $@"{RegistryEx.CLASSESROOT}\{sckValue}";
  348. }
  349. else
  350. {
  351. this.ShellPath = $@"{parentPath}\shell";
  352. }
  353. using(var shellKey = RegistryEx.GetRegistryKey(ShellPath))
  354. {
  355. if(shellKey == null) return;
  356. RegTrustedInstaller.TakeRegTreeOwnerShip(shellKey.Name);
  357. Array.ForEach(shellKey.GetSubKeyNames(), keyName =>
  358. {
  359. string regPath = $@"{ShellPath}\{keyName}";
  360. int value = Convert.ToInt32(GetValue(regPath, "CommandFlags", 0));
  361. if(value % 16 >= 8)
  362. {
  363. this.AddItem(new SeparatorItem(this, regPath));
  364. }
  365. else
  366. {
  367. this.AddItem(new SubShellItem(this, regPath));
  368. }
  369. });
  370. }
  371. }
  372. private void AddNewItem()
  373. {
  374. if(!SubShellTypeItem.CanAddMore(this)) return;
  375. using(NewShellDialog dlg = new NewShellDialog
  376. {
  377. ScenePath = this.ScenePath,
  378. ShellPath = this.ShellPath
  379. })
  380. {
  381. if(dlg.ShowDialog() != DialogResult.OK) return;
  382. this.AddItem(new SubShellItem(this, dlg.NewItemRegPath));
  383. }
  384. }
  385. private void AddSeparator()
  386. {
  387. string regPath = null;
  388. if(this.Controls.Count > 1)
  389. {
  390. regPath = GetItemRegPath((MyListItem)Controls[Controls.Count - 1]);
  391. }
  392. else
  393. {
  394. regPath = $@"{ShellPath}\Item";
  395. }
  396. regPath = ObjectPath.GetNewPathWithIndex(regPath, ObjectPath.PathType.Registry);
  397. SetValue(regPath, "CommandFlags", 0x8);
  398. this.AddItem(new SeparatorItem(this, regPath));
  399. }
  400. private void AddFromParentMenu()
  401. {
  402. if(!SubShellTypeItem.CanAddMore(this)) return;
  403. using(ShellStoreDialog dlg = new ShellStoreDialog
  404. {
  405. ShellPath = this.ParentShellPath,
  406. IgnoredKeyNames = new List<string> { this.ParentKeyName }
  407. })
  408. {
  409. if(dlg.ShowDialog() != DialogResult.OK) return;
  410. dlg.SelectedKeyNames.ForEach(keyName =>
  411. {
  412. if(!SubShellTypeItem.CanAddMore(this)) return;
  413. string srcPath = $@"{dlg.ShellPath}\{keyName}";
  414. string dstPath = ObjectPath.GetNewPathWithIndex($@"{ShellPath}\{keyName}", ObjectPath.PathType.Registry);
  415. RegistryEx.CopyTo(srcPath, dstPath);
  416. this.AddItem(new SubShellItem(this, dstPath));
  417. });
  418. }
  419. }
  420. public void MoveItem(MyListItem item, bool isUp)
  421. {
  422. int index = this.GetItemIndex(item);
  423. MyListItem otherItem = null;
  424. if(isUp)
  425. {
  426. if(index > 1)
  427. {
  428. otherItem = (MyListItem)this.Controls[index - 1];
  429. this.SetItemIndex(item, index - 1);
  430. }
  431. }
  432. else
  433. {
  434. if(index < this.Controls.Count - 1)
  435. {
  436. otherItem = (MyListItem)this.Controls[index + 1];
  437. this.SetItemIndex(item, index + 1);
  438. }
  439. }
  440. if(otherItem != null)
  441. {
  442. string path1 = GetItemRegPath(item);
  443. string path2 = GetItemRegPath(otherItem);
  444. string tempPath = ObjectPath.GetNewPathWithIndex(path1, ObjectPath.PathType.Registry);
  445. RegistryEx.MoveTo(path1, tempPath);
  446. RegistryEx.MoveTo(path2, path1);
  447. RegistryEx.MoveTo(tempPath, path2);
  448. SetItemRegPath(item, path2);
  449. SetItemRegPath(otherItem, path1);
  450. }
  451. }
  452. private string GetItemRegPath(MyListItem item)
  453. {
  454. PropertyInfo pi = item.GetType().GetProperty("RegPath");
  455. return pi.GetValue(item, null).ToString();
  456. }
  457. private void SetItemRegPath(MyListItem item, string regPath)
  458. {
  459. PropertyInfo pi = item.GetType().GetProperty("RegPath");
  460. pi.SetValue(item, regPath, null);
  461. }
  462. sealed class SubShellItem : SubShellTypeItem
  463. {
  464. public SubShellItem(PrivateMultiItemsList list, string regPath) : base(regPath)
  465. {
  466. this.Owner = list;
  467. BtnMoveUp.MouseDown += (sender, e) => Owner.MoveItem(this, true);
  468. BtnMoveDown.MouseDown += (sender, e) => Owner.MoveItem(this, false);
  469. SetItemTextValue();
  470. }
  471. public PrivateMultiItemsList Owner { get; private set; }
  472. private void SetItemTextValue()
  473. {
  474. using(var key = RegistryEx.GetRegistryKey(this.RegPath, true))
  475. {
  476. bool hasValue = false;
  477. foreach(string valueName in new[] { "MUIVerb", "" })
  478. {
  479. if(key.GetValue(valueName) != null)
  480. {
  481. hasValue = true; break;
  482. }
  483. }
  484. if(!hasValue) key.SetValue("MUIVerb", this.ItemText);
  485. }
  486. }
  487. }
  488. sealed class SeparatorItem : SubSeparatorItem
  489. {
  490. public SeparatorItem(PrivateMultiItemsList list, string regPath)
  491. {
  492. this.Owner = list;
  493. this.RegPath = regPath;
  494. BtnMoveUp.MouseDown += (sender, e) => Owner.MoveItem(this, true);
  495. BtnMoveDown.MouseDown += (sender, e) => Owner.MoveItem(this, false);
  496. }
  497. public PrivateMultiItemsList Owner { get; private set; }
  498. public string RegPath { get; private set; }
  499. public override void DeleteMe()
  500. {
  501. RegistryEx.DeleteKeyTree(this.RegPath);
  502. this.Dispose();
  503. }
  504. }
  505. }
  506. class SubSeparatorItem : MyListItem, IBtnDeleteItem, IBtnMoveUpDownItem
  507. {
  508. public SubSeparatorItem()
  509. {
  510. this.Text = AppString.Other.Separator;
  511. this.HasImage = false;
  512. BtnDelete = new DeleteButton(this);
  513. BtnMoveDown = new MoveButton(this, false);
  514. BtnMoveUp = new MoveButton(this, true);
  515. MyToolTip.SetToolTip(BtnDelete, AppString.Menu.Delete);
  516. }
  517. public DeleteButton BtnDelete { get; set; }
  518. public MoveButton BtnMoveUp { get; set; }
  519. public MoveButton BtnMoveDown { get; set; }
  520. public virtual void DeleteMe() { }
  521. }
  522. class SubShellTypeItem : ShellItem, IBtnMoveUpDownItem
  523. {
  524. public SubShellTypeItem(string regPath) : base(regPath)
  525. {
  526. BtnMoveDown = new MoveButton(this, false);
  527. BtnMoveUp = new MoveButton(this, true);
  528. this.SetCtrIndex(BtnMoveDown, 1);
  529. this.SetCtrIndex(BtnMoveUp, 2);
  530. }
  531. public MoveButton BtnMoveUp { get; set; }
  532. public MoveButton BtnMoveDown { get; set; }
  533. protected override bool IsSubItem => true;
  534. public static bool CanAddMore(MyList list)
  535. {
  536. int count = 0;
  537. foreach(Control item in list.Controls)
  538. {
  539. if(item.GetType().BaseType == typeof(SubShellTypeItem)) count++;
  540. }
  541. bool flag = count < 16;
  542. if(!flag) MessageBoxEx.Show(AppString.MessageBox.CannotAddNewItem);
  543. return flag;
  544. }
  545. }
  546. sealed class SubNewItem : NewItem
  547. {
  548. public SubNewItem(bool isPublic)
  549. {
  550. this.AddCtrs(new[] { btnAddExisting, btnAddSeparator });
  551. MyToolTip.SetToolTip(btnAddExisting, isPublic ? AppString.Tip.AddReference : AppString.Tip.AddFromParentMenu);
  552. MyToolTip.SetToolTip(btnAddSeparator, AppString.Tip.AddSeparator);
  553. btnAddExisting.MouseDown += (sender, e) => AddExisting?.Invoke(null, null);
  554. btnAddSeparator.MouseDown += (sender, e) => AddSeparator?.Invoke(null, null);
  555. }
  556. readonly PictureButton btnAddExisting = new PictureButton(AppImage.AddExisting);
  557. readonly PictureButton btnAddSeparator = new PictureButton(AppImage.AddSeparator);
  558. public event EventHandler AddExisting;
  559. public event EventHandler AddSeparator;
  560. }
  561. }
  562. }
  563. }