Fix lots of nullable warnings

This commit is contained in:
Peter Kirmeier 2023-04-16 23:27:27 +02:00
parent 1d29fea766
commit 6993aef3ac
21 changed files with 848 additions and 776 deletions

View file

@ -5,6 +5,7 @@
namespace SystemTrayMenu.Handler
{
using System;
using System.Data.Common;
using System.Globalization;
using System.Linq;
using System.Windows.Input;
@ -33,7 +34,7 @@ namespace SystemTrayMenu.Handler
internal event Action<ListView, int>? RowSelected;
internal event Action<int, ListView>? RowDeselected;
internal event Action<int, ListView?>? RowDeselected;
internal event Action<ListView, int>? EnterPressed;
@ -169,10 +170,10 @@ namespace SystemTrayMenu.Handler
break;
}
int GetMenuIndex(in Menu currentMenu)
int GetMenuIndex(in Menu? currentMenu)
{
int index = 0;
foreach (Menu menuFindIndex in menus.Where(m => m != null))
foreach (Menu? menuFindIndex in menus.Where(m => m != null))
{
if (currentMenu == menuFindIndex)
{
@ -252,7 +253,7 @@ namespace SystemTrayMenu.Handler
ref Menu? menuFromSelected,
ref string textselected)
{
Menu menu = menus[iMenuKey];
Menu? menu = menus[iMenuKey];
bool isStillSelected = false;
if (menu != null &&
iRowKey > -1)
@ -282,7 +283,7 @@ namespace SystemTrayMenu.Handler
int iRowBefore = iRowKey;
int iMenuBefore = iMenuKey;
Menu menu = menus[iMenuKey];
Menu? menu = menus[iMenuKey];
ListView? dgv = null;
ListView? dgvBefore = null;
Menu? menuFromSelected = null;
@ -305,7 +306,7 @@ namespace SystemTrayMenu.Handler
{
ResetSelectedByKey();
menu = menus[iMenuKey];
dgv = menu.GetDataGridView();
dgv = menu?.GetDataGridView();
}
bool toClear = false;
@ -313,7 +314,7 @@ namespace SystemTrayMenu.Handler
switch (key)
{
case Key.Enter:
if ((modifiers == ModifierKeys.None) && (iRowKey > -1 && dgv.Items.Count > iRowKey))
if ((modifiers == ModifierKeys.None) && (iRowKey > -1 && dgv != null && dgv.Items.Count > iRowKey))
{
RowData trigger = ((Menu.ListViewItemData)dgv.Items[iRowKey]).data;
if (trigger.IsMenuOpen || !trigger.ContainsMenu)
@ -340,9 +341,9 @@ namespace SystemTrayMenu.Handler
}
else
{
RowDeselected(iRowBefore, dgvBefore);
RowDeselected?.Invoke(iRowBefore, dgvBefore);
SelectRow(dgv, iRowKey);
EnterPressed.Invoke(dgv, iRowKey);
EnterPressed?.Invoke(dgv, iRowKey);
}
handled = true;
@ -351,10 +352,11 @@ namespace SystemTrayMenu.Handler
break;
case Key.Up:
if ((modifiers == ModifierKeys.None) &&
dgv != null &&
(SelectMatchedReverse(dgv, iRowKey) ||
SelectMatchedReverse(dgv, dgv.Items.Count - 1)))
{
RowDeselected(iRowBefore, dgvBefore);
RowDeselected?.Invoke(iRowBefore, dgvBefore);
SelectRow(dgv, iRowKey);
toClear = true;
handled = true;
@ -366,7 +368,7 @@ namespace SystemTrayMenu.Handler
(SelectMatched(dgv, iRowKey) ||
SelectMatched(dgv, 0)))
{
RowDeselected(iRowBefore, dgvBefore);
RowDeselected?.Invoke(iRowBefore, dgvBefore);
SelectRow(dgv, iRowKey);
toClear = true;
handled = true;
@ -376,7 +378,7 @@ namespace SystemTrayMenu.Handler
case Key.Home:
if ((modifiers == ModifierKeys.None) && SelectMatched(dgv, 0))
{
RowDeselected(iRowBefore, dgvBefore);
RowDeselected?.Invoke(iRowBefore, dgvBefore);
SelectRow(dgv, iRowKey);
toClear = true;
handled = true;
@ -384,9 +386,11 @@ namespace SystemTrayMenu.Handler
break;
case Key.End:
if ((modifiers == ModifierKeys.None) && SelectMatchedReverse(dgv, dgv.Items.Count - 1))
if ((modifiers == ModifierKeys.None) &&
dgv != null &&
SelectMatchedReverse(dgv, dgv.Items.Count - 1))
{
RowDeselected(iRowBefore, dgvBefore);
RowDeselected?.Invoke(iRowBefore, dgvBefore);
SelectRow(dgv, iRowKey);
toClear = true;
handled = true;
@ -394,10 +398,14 @@ namespace SystemTrayMenu.Handler
break;
case Key.Left:
if (modifiers == ModifierKeys.None)
if (modifiers == ModifierKeys.None &&
dgv != null &&
dgvBefore != null)
{
bool nextMenuLocationIsLeft = menus[iMenuKey + 1] != null && menus[iMenuKey + 1].Location.X < menus[iMenuKey].Location.X;
bool previousMenuLocationIsRight = iMenuKey > 0 && menus[iMenuKey]?.Location.X < menus[iMenuKey - 1]?.Location.X;
Menu? nextMenu = menus[iMenuKey + 1];
bool nextMenuLocationIsLeft = nextMenu != null && menu != null && nextMenu.Location.X < menu.Location.X;
Menu? previousMenu = menus[iMenuKey - 1];
bool previousMenuLocationIsRight = iMenuKey > 0 && previousMenu != null && menu != null && menu.Location.X < previousMenu.Location.X;
if (nextMenuLocationIsLeft || previousMenuLocationIsRight)
{
SelectNextMenu(iRowBefore, ref dgv, dgvBefore, menuFromSelected, isStillSelected, ref toClear);
@ -412,7 +420,9 @@ namespace SystemTrayMenu.Handler
break;
case Key.Right:
if (modifiers == ModifierKeys.None)
if (modifiers == ModifierKeys.None &&
dgv != null &&
dgvBefore != null)
{
bool nextMenuLocationIsRight = menus[iMenuKey + 1]?.Location.X > menus[iMenuKey]?.Location.X;
bool previousMenuLocationIsLeft = iMenuKey > 0 && menus[iMenuKey]?.Location.X > menus[iMenuKey - 1]?.Location.X;
@ -434,7 +444,7 @@ namespace SystemTrayMenu.Handler
if ((key == Key.Escape && modifiers == ModifierKeys.None) ||
(key == Key.F4 && modifiers == ModifierKeys.Alt))
{
RowDeselected(iRowBefore, dgvBefore);
RowDeselected?.Invoke(iRowBefore, dgvBefore);
iMenuKey = 0;
iRowKey = -1;
toClear = true;
@ -455,7 +465,7 @@ namespace SystemTrayMenu.Handler
if (SelectMatched(dgv, iRowKey, keyInput) ||
SelectMatched(dgv, 0, keyInput))
{
RowDeselected(iRowBefore, null);
RowDeselected?.Invoke(iRowBefore, null);
SelectRow(dgv, iRowKey);
toClear = true;
}
@ -465,7 +475,7 @@ namespace SystemTrayMenu.Handler
if (SelectMatched(dgv, iRowKey, keyInput) ||
SelectMatched(dgv, 0, keyInput))
{
RowDeselected(iRowBefore, null);
RowDeselected?.Invoke(iRowBefore, null);
SelectRow(dgv, iRowKey);
}
else
@ -482,7 +492,7 @@ namespace SystemTrayMenu.Handler
}
}
private void SelectPreviousMenu(int iRowBefore, ref Menu menu, ref ListView dgv, ListView dgvBefore, ref bool toClear)
private void SelectPreviousMenu(int iRowBefore, ref Menu? menu, ref ListView? dgv, ListView? dgvBefore, ref bool toClear)
{
if (iMenuKey > 0)
{
@ -491,20 +501,22 @@ namespace SystemTrayMenu.Handler
iMenuKey -= 1;
iRowKey = -1;
menu = menus[iMenuKey];
dgv = menu.GetDataGridView();
if (SelectMatched(dgv, dgv.Items.IndexOf(dgv.SelectedItems.Count > 0 ? dgv.SelectedItems[0] : null)) ||
SelectMatched(dgv, 0))
dgv = menu?.GetDataGridView();
if (dgv != null)
{
RowDeselected(iRowBefore, dgvBefore);
SelectRow(dgv, iRowKey);
toClear = true;
if (SelectMatched(dgv, dgv.Items.IndexOf(dgv.SelectedItems.Count > 0 ? dgv.SelectedItems[0] : null)) ||
SelectMatched(dgv, 0))
{
RowDeselected?.Invoke(iRowBefore, dgvBefore);
SelectRow(dgv, iRowKey);
toClear = true;
}
}
}
}
else
{
RowDeselected(iRowBefore, dgvBefore);
RowDeselected?.Invoke(iRowBefore, dgvBefore);
iMenuKey = 0;
iRowKey = -1;
toClear = true;
@ -512,7 +524,7 @@ namespace SystemTrayMenu.Handler
}
}
private void SelectNextMenu(int iRowBefore, ref ListView dgv, ListView dgvBefore, Menu menuFromSelected, bool isStillSelected, ref bool toClear)
private void SelectNextMenu(int iRowBefore, ref ListView? dgv, ListView dgvBefore, Menu? menuFromSelected, bool isStillSelected, ref bool toClear)
{
int iMenuKeyNext = iMenuKey + 1;
if (isStillSelected)
@ -520,15 +532,15 @@ namespace SystemTrayMenu.Handler
if (menuFromSelected != null &&
menuFromSelected == menus[iMenuKeyNext])
{
dgv = menuFromSelected.GetDataGridView();
if (dgv.Items.Count > 0)
dgv = menuFromSelected?.GetDataGridView();
if (dgv != null && dgv.Items.Count > 0)
{
iMenuKey += 1;
iRowKey = -1;
if (SelectMatched(dgv, iRowKey) ||
SelectMatched(dgv, 0))
{
RowDeselected(iRowBefore, dgvBefore);
RowDeselected?.Invoke(iRowBefore, dgvBefore);
SelectRow(dgv, iRowKey);
toClear = true;
}
@ -539,13 +551,14 @@ namespace SystemTrayMenu.Handler
{
iRowKey = -1;
iMenuKey = menus.Where(m => m != null).Count() - 1;
if (menus[iMenuKey] != null)
Menu? lastMenu = menus[iMenuKey];
if (lastMenu != null)
{
dgv = menus[iMenuKey].GetDataGridView();
dgv = lastMenu?.GetDataGridView();
if (SelectMatched(dgv, iRowKey) ||
SelectMatched(dgv, 0))
{
RowDeselected(iRowBefore, dgvBefore);
RowDeselected?.Invoke(iRowBefore, dgvBefore);
SelectRow(dgv, iRowKey);
toClear = true;
}
@ -553,28 +566,34 @@ namespace SystemTrayMenu.Handler
}
}
private void SelectRow(ListView dgv, int iRowKey)
private void SelectRow(ListView? dgv, int iRowKey)
{
InUse = true;
RowSelected(dgv, iRowKey);
if (dgv != null)
{
InUse = true;
RowSelected?.Invoke(dgv, iRowKey);
}
}
private bool SelectMatched(ListView dgv, int indexStart, string keyInput = "")
private bool SelectMatched(ListView? dgv, int indexStart, string keyInput = "")
{
bool found = false;
for (int i = indexStart; i < dgv.Items.Count; i++)
if (dgv != null)
{
if (Select(dgv, i, keyInput))
for (int i = indexStart; i < dgv.Items.Count; i++)
{
found = true;
break;
if (Select(dgv, i, keyInput))
{
found = true;
break;
}
}
}
return found;
}
private bool SelectMatchedReverse(ListView dgv, int indexStart, string keyInput = "")
private bool SelectMatchedReverse(ListView? dgv, int indexStart, string keyInput = "")
{
bool found = false;
for (int i = indexStart; i > -1; i--)
@ -589,11 +608,12 @@ namespace SystemTrayMenu.Handler
return found;
}
private bool Select(ListView dgv, int i, string keyInput = "")
private bool Select(ListView? dgv, int i, string keyInput = "")
{
bool found = false;
if (i > -1 &&
i != iRowKey &&
dgv != null &&
dgv.Items.Count > i)
{
Menu.ListViewItemData itemData = (Menu.ListViewItemData)dgv.Items[i];
@ -619,11 +639,11 @@ namespace SystemTrayMenu.Handler
private void ClearIsSelectedByKey(int menuIndex, int rowIndex)
{
Menu menu = menus[menuIndex];
Menu? menu = menus[menuIndex];
if (menu != null && rowIndex > -1)
{
ListView dgv = menu.GetDataGridView();
if (dgv.Items.Count > rowIndex)
ListView? dgv = menu?.GetDataGridView();
if (dgv != null && dgv.Items.Count > rowIndex)
{
Menu.ListViewItemData itemData = (Menu.ListViewItemData)dgv.Items[rowIndex];
RowData rowData = itemData.data;

View file

@ -326,7 +326,7 @@ namespace SystemTrayMenu.Business
private bool IsMainUsable => menus[0]?.IsUsable ?? false;
private IEnumerable<Menu> AsEnumerable => menus.Where(m => m != null && !m.IsDisposed) !;
private IEnumerable<Menu> AsEnumerable => menus.Where(m => m != null && !m.IsDisposed)!;
private List<Menu> AsList => AsEnumerable.ToList();

View file

@ -16,9 +16,9 @@ namespace SystemTrayMenu.Handler
internal class WaitToLoadMenu : IDisposable
{
private readonly DispatcherTimer timerStartLoad = new();
private ListView dgv;
private ListView? dgv;
private int rowIndex;
private ListView dgvTmp;
private ListView? dgvTmp;
private int rowIndexTmp;
private bool alreadyOpened;
@ -32,13 +32,13 @@ namespace SystemTrayMenu.Handler
timerStartLoad.Tick += WaitStartLoad_Tick;
}
internal event Action<RowData> StartLoadMenu;
internal event Action<RowData>? StartLoadMenu;
internal event Action<int> CloseMenu;
internal event Action<int>? CloseMenu;
internal event Action StopLoadMenu;
internal event Action? StopLoadMenu;
internal event Action<ListView, int> MouseEnterOk;
internal event Action<ListView, int>? MouseEnterOk;
internal bool MouseActive { get; set; }
@ -58,7 +58,7 @@ namespace SystemTrayMenu.Handler
{
if (dgv.Items.Count > rowIndex)
{
MouseEnterOk(dgv, rowIndex);
MouseEnterOk?.Invoke(dgv, rowIndex);
timerStartLoad.Stop();
StopLoadMenu?.Invoke();
checkForMouseActive = true;
@ -96,7 +96,7 @@ namespace SystemTrayMenu.Handler
}
}
internal void RowDeselected(int rowIndex, ListView dgv)
internal void RowDeselected(int rowIndex, ListView? dgv)
{
timerStartLoad.Stop();
StopLoadMenu?.Invoke();
@ -136,11 +136,11 @@ namespace SystemTrayMenu.Handler
if (mouseMoveEvents > 6)
{
MouseActive = true;
if (dgvTmp != null
#if TODO // WPF: Can be optimized away?
&& !dgvTmp.IsDisposed
if (dgvTmp != null && !dgvTmp.IsDisposed)
#else
if (dgvTmp != null)
#endif
)
{
MouseEnter(dgvTmp, rowIndexTmp);
}
@ -160,7 +160,7 @@ namespace SystemTrayMenu.Handler
}
}
private void WaitStartLoad_Tick(object sender, EventArgs e)
private void WaitStartLoad_Tick(object? sender, EventArgs e)
{
timerStartLoad.Stop();
if (!checkForMouseActive || MouseActive)
@ -171,7 +171,7 @@ namespace SystemTrayMenu.Handler
private void CallOpenMenuNow()
{
if (dgv.Items.Count > rowIndex && !alreadyOpened)
if (!alreadyOpened && dgv != null && dgv.Items.Count > rowIndex)
{
alreadyOpened = true;
@ -180,16 +180,16 @@ namespace SystemTrayMenu.Handler
rowData.Level = menu.Level;
if (rowData.ContainsMenu)
{
CloseMenu.Invoke(rowData.Level + 2);
CloseMenu?.Invoke(rowData.Level + 2);
}
CloseMenu.Invoke(rowData.Level + 1);
CloseMenu?.Invoke(rowData.Level + 1);
if (!rowData.IsContextMenuOpen &&
rowData.ContainsMenu &&
rowData.Level + 1 < MenuDefines.MenusMax)
{
StartLoadMenu.Invoke(rowData);
StartLoadMenu?.Invoke(rowData);
}
}
}
@ -215,7 +215,7 @@ namespace SystemTrayMenu.Handler
dgv.SelectedIndex = rowIndex;
}
private void ResetData(ListView dgv, int rowIndex)
private void ResetData(ListView? dgv, int rowIndex)
{
if (dgv != null && dgv.Items.Count > rowIndex)
{

View file

@ -227,7 +227,7 @@ namespace SystemTrayMenu.DataClasses
}
}
internal void MouseClick(MouseEventArgs e, out bool toCloseByDoubleClick)
internal void MouseClick(MouseEventArgs? e, out bool toCloseByDoubleClick)
{
IsClicking = false;
toCloseByDoubleClick = false;
@ -267,7 +267,7 @@ namespace SystemTrayMenu.DataClasses
}
}
private void OpenItem(MouseEventArgs e, ref bool toCloseByOpenItem)
private void OpenItem(MouseEventArgs? e, ref bool toCloseByOpenItem)
{
if (!ContainsMenu && Path != null && ResolvedPath != null &&
(e == null || e.LeftButton == MouseButtonState.Pressed))

View file

@ -1,16 +1,18 @@
// <copyright file="GlobalSuppressions.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA0001:XML comment analysis is disabled due to project configuration", Justification = "no idea what this is")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "we need to document")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1601:Partial elements should be documented", Justification = "we need to document")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items should be documented", Justification = "we need to document")]
[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1000:Keywords should be spaced correctly", Justification = "new() should not be replaced by new() ")]
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "Standard codecleanup removes the this")]
[assembly: SuppressMessage("Interoperability", "CA1416:Check platform compatibility", Justification = "this is a long way to get platform compatibility")]
// <copyright file="GlobalSuppressions.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA0001:XML comment analysis is disabled due to project configuration", Justification = "no idea what this is")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "we need to document")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1601:Partial elements should be documented", Justification = "we need to document")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items should be documented", Justification = "we need to document")]
[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1000:Keywords should be spaced correctly", Justification = "new() should not be replaced by new() ")]
[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1009:Closing parenthesis should be spaced correctly", Justification = "Weird look when used by null-forgiving operator")]
[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:Closing square brackets should be spaced correctly", Justification = "Conflicts with SA1018:A nullable type symbol within a C# element is not spaced correctly.")]
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "Standard codecleanup removes the this")]
[assembly: SuppressMessage("Interoperability", "CA1416:Check platform compatibility", Justification = "this is a long way to get platform compatibility")]

View file

@ -38,10 +38,16 @@ namespace SystemTrayMenu.Helpers
string path;
if (menu != null)
{
RowData rowData = menu.RowDataParent;
RowData? rowData = menu.RowDataParent;
if (rowData != null)
{
path = rowData.ResolvedPath;
string? resolvedPath = rowData.ResolvedPath;
if (string.IsNullOrEmpty(resolvedPath))
{
return;
}
path = resolvedPath;
}
else
{
@ -54,7 +60,11 @@ namespace SystemTrayMenu.Helpers
}
object data = e.Data.GetData("UniformResourceLocator");
MemoryStream ms = data as MemoryStream;
if (data is not MemoryStream ms)
{
return;
}
byte[] bytes = ms.ToArray();
Encoding encod = Encoding.ASCII;
string url = encod.GetString(bytes);
@ -107,7 +117,7 @@ namespace SystemTrayMenu.Helpers
return value;
}
private static void WriteShortcut(string url, string pathIcon, string fileNamePathShortcut)
private static void WriteShortcut(string url, string? pathIcon, string fileNamePathShortcut)
{
try
{

View file

@ -37,7 +37,7 @@ namespace SystemTrayMenu.Helpers
/// <summary>
/// A hot key has been pressed.
/// </summary>
internal event EventHandler<KeyPressedEventArgs> KeyPressed;
internal event EventHandler<KeyPressedEventArgs>? KeyPressed;
public void Dispose()
{
@ -52,16 +52,6 @@ namespace SystemTrayMenu.Helpers
window.Dispose();
}
/// <summary>
/// Registers a hot key in the system.
/// </summary>
/// <param name="key">The key itself that is associated with the hot key.</param>
internal void RegisterHotKey(Key key)
{
uint keyModifiersNone = 0;
RegisterHotKey(keyModifiersNone, key);
}
internal void RegisterHotKey()
{
KeyboardHookModifierKeys modifiers = KeyboardHookModifierKeys.None;
@ -97,6 +87,16 @@ namespace SystemTrayMenu.Helpers
#endif
}
/// <summary>
/// Registers a hot key in the system.
/// </summary>
/// <param name="key">The key itself that is associated with the hot key.</param>
internal void RegisterHotKey(Key key)
{
uint keyModifiersNone = 0;
RegisterHotKey(keyModifiersNone, key);
}
/// <summary>
/// Registers a hot key in the system.
/// </summary>
@ -107,7 +107,7 @@ namespace SystemTrayMenu.Helpers
RegisterHotKey((uint)modifier, key);
}
private void Window_KeyPressed(object sender, KeyPressedEventArgs e)
private void Window_KeyPressed(object? sender, KeyPressedEventArgs e)
{
KeyPressed?.Invoke(this, e);
}
@ -131,7 +131,7 @@ namespace SystemTrayMenu.Helpers
{
private const int WmHotkey = 0x0312;
public event EventHandler<KeyPressedEventArgs> KeyPressed;
public event EventHandler<KeyPressedEventArgs>? KeyPressed;
/// <summary>
/// Overridden to get the notifications.

View file

@ -14,8 +14,8 @@ namespace SystemTrayMenu.Helpers.Updater
public class GitHubUpdate
{
private static List<Dictionary<string, object>> releases;
private static UpdateWindow newVersionForm;
private static List<Dictionary<string, object>>? releases;
private static UpdateWindow? newVersionWindow;
/// <summary>
/// Gets the latest release version name .
@ -33,7 +33,7 @@ namespace SystemTrayMenu.Helpers.Updater
try
{
result = releases[0]["tag_name"].ToString() !.Replace("v", string.Empty); // 0 = latest
result = releases[0]["tag_name"].ToString()!.Replace("v", string.Empty); // 0 = latest
}
catch (Exception ex)
{
@ -51,9 +51,9 @@ namespace SystemTrayMenu.Helpers.Updater
public static void ActivateNewVersionFormOrCheckForUpdates(bool showWhenUpToDate)
{
if (newVersionForm != null)
if (newVersionWindow != null)
{
newVersionForm!.HandleInvoke(() => newVersionForm?.Activate());
newVersionWindow!.HandleInvoke(() => newVersionWindow?.Activate());
}
else
{
@ -67,7 +67,7 @@ namespace SystemTrayMenu.Helpers.Updater
HttpClient client = new();
// https://developer.github.com/v3/#user-agent-required
client.DefaultRequestHeaders.Add("User-Agent", "SystemTrayMenu/" + Assembly.GetExecutingAssembly().GetName().Version.ToString());
client.DefaultRequestHeaders.Add("User-Agent", "SystemTrayMenu/" + Assembly.GetExecutingAssembly().GetName().Version!.ToString());
// https://developer.github.com/v3/media/#request-specific-version
client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3.text+json");
@ -97,32 +97,43 @@ namespace SystemTrayMenu.Helpers.Updater
private static void RemoveCurrentAndOlderVersions()
{
int releasesCount = releases.Count;
Version versionCurrent = Assembly.GetExecutingAssembly().GetName().Version;
for (int i = 0; i < releasesCount; i++)
if (releases != null)
{
string tagName = releases[i]["tag_name"].ToString();
Version versionGitHub = new(tagName.Replace("v", string.Empty));
if (versionGitHub.CompareTo(versionCurrent) < 1)
int releasesCount = releases.Count;
Version versionCurrent = Assembly.GetExecutingAssembly().GetName().Version!;
for (int i = 0; i < releasesCount; i++)
{
releases.RemoveRange(i, releasesCount - i);
break;
string? tagName = releases[i]["tag_name"].ToString();
if (tagName == null)
{
continue;
}
Version versionGitHub = new(tagName.Replace("v", string.Empty));
if (versionGitHub.CompareTo(versionCurrent) < 1)
{
releases.RemoveRange(i, releasesCount - i);
break;
}
}
}
}
private static void ShowNewVersionOrUpToDateDialog(bool showWhenUpToDate)
{
if (releases.Count > 0)
if (releases != null)
{
newVersionForm = new();
newVersionForm.textBox.Text = GetChangelog();
newVersionForm.Closed += (_, _) => newVersionForm = null;
newVersionForm.ShowDialog();
}
else if (showWhenUpToDate)
{
MessageBox.Show(Translator.GetText("You have the latest version of SystemTrayMenu!"));
if (releases.Count > 0)
{
newVersionWindow = new();
newVersionWindow.textBox.Text = GetChangelog();
newVersionWindow.Closed += (_, _) => newVersionWindow = null;
newVersionWindow.ShowDialog();
}
else if (showWhenUpToDate)
{
MessageBox.Show(Translator.GetText("You have the latest version of SystemTrayMenu!"));
}
}
}
@ -145,10 +156,15 @@ namespace SystemTrayMenu.Helpers.Updater
for (int i = 0; i < releases.Count; i++)
{
Dictionary<string, object> release = releases[i];
string? bodyText = release["body_text"].ToString();
if (bodyText == null)
{
continue;
}
result += release["name"].ToString()
+ Environment.NewLine
+ release["body_text"].ToString()
+ bodyText
.Replace("\n\n", Environment.NewLine)
.Replace("\n \n", Environment.NewLine)
+ Environment.NewLine + Environment.NewLine;

View file

@ -172,7 +172,7 @@ namespace SystemTrayMenu.Helpers.Updater
if (type.IsArray)
{
Type arrayType = type.GetElementType() !;
Type arrayType = type.GetElementType()!;
if (json[0] != '[' || json[^1] != ']')
{
return null;

View file

@ -1,48 +1,50 @@
// <copyright file="CustomSettingsProvider.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace SystemTrayMenu.Properties
{
using System;
using System.Collections.Generic;
// <copyright file="CustomSettingsProvider.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace SystemTrayMenu.Properties
{
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using SystemTrayMenu.Utilities;
internal class CustomSettingsProvider : SettingsProvider
{
private const string NameOf = "name";
private const string SerializeAs = "serializeAs";
private const string Config = "configuration";
private const string UserSettings = "userSettings";
private const string Setting = "setting";
private bool loaded;
/// <summary>
/// Initializes a new instance of the <see cref="CustomSettingsProvider"/> class.
/// Loads the file into memory.
/// </summary>
public CustomSettingsProvider()
{
SettingsDictionary = new Dictionary<string, SettingStruct>();
}
/// <summary>
/// Gets the setting key this is returning must set before the settings are used.
/// e.g. <c>Properties.Settings.Default.SettingsKey = @"C:\temp\user.config";</c>.
/// </summary>
public static string UserConfigPath => Path.Combine(
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
$"SystemTrayMenu"),
$"user-{Environment.MachineName}.config");
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using SystemTrayMenu.Utilities;
internal class CustomSettingsProvider : SettingsProvider
{
private const string NameOf = "name";
private const string SerializeAs = "serializeAs";
private const string Config = "configuration";
private const string UserSettings = "userSettings";
private const string Setting = "setting";
private static readonly string? SettingsFullTypeName = typeof(Settings).FullName;
private bool loaded;
/// <summary>
/// Initializes a new instance of the <see cref="CustomSettingsProvider"/> class.
/// Loads the file into memory.
/// </summary>
public CustomSettingsProvider()
{
SettingsDictionary = new Dictionary<string, SettingStruct>();
}
/// <summary>
/// Gets the setting key this is returning must set before the settings are used.
/// e.g. <c>Properties.Settings.Default.SettingsKey = @"C:\temp\user.config";</c>.
/// </summary>
public static string UserConfigPath => Path.Combine(
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
$"SystemTrayMenu"),
$"user-{Environment.MachineName}.config");
public static string? ConfigPathAssembly
{
get
@ -59,87 +61,87 @@ namespace SystemTrayMenu.Properties
return null;
}
}
/// <summary>
/// Gets or sets override.
/// </summary>
public override string ApplicationName
{
get => Assembly.GetExecutingAssembly().ManifestModule.Name;
set
{
// do nothing
}
}
/// <summary>
/// Gets or sets in memory storage of the settings values.
/// </summary>
private Dictionary<string, SettingStruct> SettingsDictionary { get; set; }
public static bool IsActivatedConfigPathAssembly()
{
return IsConfigPathAssembly();
}
public static void ActivateConfigPathAssembly()
{
CreateEmptyConfigIfNotExists(ConfigPathAssembly);
}
public static void DeactivateConfigPathAssembly()
{
if (IsConfigPathAssembly())
{
try
{
File.Delete(ConfigPathAssembly!);
}
catch (Exception ex)
{
Log.Warn($"Could not delete {ConfigPathAssembly}", ex);
}
}
}
/// <summary>
/// Override.
/// </summary>
/// <param name="name">name.</param>
/// <param name="config">config.</param>
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
base.Initialize(ApplicationName, config);
}
/// <summary>
/// Must override this, this is the bit that matches up the designer properties to the dictionary values.
/// </summary>
/// <param name="context">context.</param>
/// <param name="collection">collection.</param>
/// <returns>SettingsPropertyValueCollection.</returns>
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
{
// load the file
if (!loaded)
{
loaded = true;
LoadValuesFromFile();
}
// collection that will be returned.
SettingsPropertyValueCollection values = new();
// itterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
foreach (SettingsProperty setting in collection)
{
SettingsPropertyValue value = new(setting)
{
IsDirty = false,
};
}
/// <summary>
/// Gets or sets override.
/// </summary>
public override string ApplicationName
{
get => Assembly.GetExecutingAssembly().ManifestModule.Name;
set
{
// do nothing
}
}
/// <summary>
/// Gets or sets in memory storage of the settings values.
/// </summary>
private Dictionary<string, SettingStruct> SettingsDictionary { get; set; }
public static bool IsActivatedConfigPathAssembly()
{
return IsConfigPathAssembly();
}
public static void ActivateConfigPathAssembly()
{
CreateEmptyConfigIfNotExists(ConfigPathAssembly);
}
public static void DeactivateConfigPathAssembly()
{
if (IsConfigPathAssembly())
{
try
{
File.Delete(ConfigPathAssembly!);
}
catch (Exception ex)
{
Log.Warn($"Could not delete {ConfigPathAssembly}", ex);
}
}
}
/// <summary>
/// Override.
/// </summary>
/// <param name="name">name.</param>
/// <param name="config">config.</param>
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
base.Initialize(ApplicationName, config);
}
/// <summary>
/// Must override this, this is the bit that matches up the designer properties to the dictionary values.
/// </summary>
/// <param name="context">context.</param>
/// <param name="collection">collection.</param>
/// <returns>SettingsPropertyValueCollection.</returns>
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
{
// load the file
if (!loaded)
{
loaded = true;
LoadValuesFromFile();
}
// collection that will be returned.
SettingsPropertyValueCollection values = new();
// itterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
foreach (SettingsProperty setting in collection)
{
SettingsPropertyValue value = new(setting)
{
IsDirty = false,
};
// need the type of the value for the strong typing
string? typename = setting.PropertyType.FullName;
if (typename != null)
@ -161,143 +163,155 @@ namespace SystemTrayMenu.Properties
values.Add(value);
}
}
}
return values;
}
/// <summary>
/// Must override this, this is the bit that does the saving to file. Called when Settings.Save() is called.
/// </summary>
/// <param name="context">context.</param>
/// <param name="collection">collection.</param>
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
{
// grab the values from the collection parameter and update the values in our dictionary.
foreach (SettingsPropertyValue value in collection)
{
SettingStruct setting = new()
{
Value = value.PropertyValue == null ? string.Empty : value.PropertyValue.ToString() ?? string.Empty,
Name = value.Name,
SerializeAs = value.Property.SerializeAs.ToString(),
};
if (!SettingsDictionary.ContainsKey(value.Name))
{
SettingsDictionary.Add(value.Name, setting);
}
else
{
SettingsDictionary[value.Name] = setting;
}
}
// now that our local dictionary is up-to-date, save it to disk.
SaveValuesToFile();
}
/// <summary>
/// Creates an empty user.config file...looks like the one MS creates.
/// This could be overkill a simple key/value pairing would probably do.
/// </summary>
private static void CreateEmptyConfigIfNotExists(string? path)
{
if (!File.Exists(path))
{
// if the config file is not where it's supposed to be create a new one.
XDocument doc = new();
XDeclaration declaration = new("1.0", "utf-8", "true");
XElement config = new(Config);
XElement userSettings = new(UserSettings);
XElement group = new(typeof(Settings).FullName);
userSettings.Add(group);
config.Add(userSettings);
doc.Add(config);
doc.Declaration = declaration;
}
}
return values;
}
/// <summary>
/// Must override this, this is the bit that does the saving to file. Called when Settings.Save() is called.
/// </summary>
/// <param name="context">context.</param>
/// <param name="collection">collection.</param>
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
{
// grab the values from the collection parameter and update the values in our dictionary.
foreach (SettingsPropertyValue value in collection)
{
SettingStruct setting = new()
{
Value = value.PropertyValue == null ? string.Empty : value.PropertyValue.ToString() ?? string.Empty,
Name = value.Name,
SerializeAs = value.Property.SerializeAs.ToString(),
};
if (!SettingsDictionary.ContainsKey(value.Name))
{
SettingsDictionary.Add(value.Name, setting);
}
else
{
SettingsDictionary[value.Name] = setting;
}
}
// now that our local dictionary is up-to-date, save it to disk.
SaveValuesToFile();
}
/// <summary>
/// Creates an empty user.config file...looks like the one MS creates.
/// This could be overkill a simple key/value pairing would probably do.
/// </summary>
private static void CreateEmptyConfigIfNotExists(string? path)
{
if (!File.Exists(path))
{
if (string.IsNullOrEmpty(SettingsFullTypeName))
{
Log.Warn($"Failed to store config for group {SettingsFullTypeName ?? "<null>"}", new());
return;
}
string? dir = Path.GetDirectoryName(path);
if (string.IsNullOrEmpty(dir) || string.IsNullOrEmpty(path))
{
Log.Warn($"Failed to store config in directory {path ?? "<null>"}", new());
return;
}
// if the config file is not where it's supposed to be create a new one.
XDocument doc = new();
XDeclaration declaration = new("1.0", "utf-8", "true");
XElement config = new(Config);
XElement userSettings = new(UserSettings);
XElement group = new(SettingsFullTypeName);
userSettings.Add(group);
config.Add(userSettings);
doc.Add(config);
doc.Declaration = declaration;
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir!);
Directory.CreateDirectory(dir);
}
try
{
doc.Save(path);
}
catch (Exception ex)
{
Log.Warn($"Failed to store config at assembly location {path}", ex);
}
}
try
{
doc.Save(path);
}
catch (Exception ex)
{
Log.Warn($"Failed to store config at assembly location {path}", ex);
}
}
}
private static bool IsConfigPathAssembly()
{
bool isconfigPathAssembly = false;
try
{
isconfigPathAssembly = File.Exists(ConfigPathAssembly);
}
catch (Exception ex)
{
Log.Warn("IsConfigPathAssembly failed", ex);
}
return isconfigPathAssembly;
}
private static XDocument? LoadOrGetNew(string path)
{
XDocument? xDocument = null;
try
{
xDocument = XDocument.Load(path);
}
catch (Exception exceptionWarning)
{
Log.Warn($"Could not load {path}", exceptionWarning);
try
{
File.Delete(path);
CreateEmptyConfigIfNotExists(path);
xDocument = XDocument.Load(path);
}
catch (Exception exceptionError)
{
Log.Error($"Could not delete and create {path}", exceptionError);
}
}
return xDocument;
}
/// <summary>
/// Loads the values of the file into memory.
/// </summary>
private void LoadValuesFromFile()
{
CreateEmptyConfigIfNotExists(UserConfigPath);
// load the xml
XDocument? configXml;
if (IsConfigPathAssembly())
{
configXml = LoadOrGetNew(ConfigPathAssembly!);
}
else
{
configXml = LoadOrGetNew(UserConfigPath);
}
if (configXml != null)
{
// get all of the <setting name="..." serializeAs="..."> elements.
IEnumerable<XElement>? settingElements = configXml.Element(Config)?.Element(UserSettings)?.Element(typeof(Settings).FullName)?.Elements(Setting);
// iterate through, adding them to the dictionary, (checking for nulls, xml no likey nulls)
private static bool IsConfigPathAssembly()
{
bool isconfigPathAssembly = false;
try
{
isconfigPathAssembly = File.Exists(ConfigPathAssembly);
}
catch (Exception ex)
{
Log.Warn("IsConfigPathAssembly failed", ex);
}
return isconfigPathAssembly;
}
private static XDocument? LoadOrGetNew(string path)
{
XDocument? xDocument = null;
try
{
xDocument = XDocument.Load(path);
}
catch (Exception exceptionWarning)
{
Log.Warn($"Could not load {path}", exceptionWarning);
try
{
File.Delete(path);
CreateEmptyConfigIfNotExists(path);
xDocument = XDocument.Load(path);
}
catch (Exception exceptionError)
{
Log.Error($"Could not delete and create {path}", exceptionError);
}
}
return xDocument;
}
/// <summary>
/// Loads the values of the file into memory.
/// </summary>
private void LoadValuesFromFile()
{
CreateEmptyConfigIfNotExists(UserConfigPath);
// load the xml
XDocument? configXml;
if (IsConfigPathAssembly())
{
configXml = LoadOrGetNew(ConfigPathAssembly!);
}
else
{
configXml = LoadOrGetNew(UserConfigPath);
}
if (configXml != null && !string.IsNullOrEmpty(SettingsFullTypeName))
{
// get all of the <setting name="..." serializeAs="..."> elements.
IEnumerable<XElement>? settingElements = configXml.Element(Config)?.Element(UserSettings)?.Element(SettingsFullTypeName)?.Elements(Setting);
// iterate through, adding them to the dictionary, (checking for nulls, xml no likey nulls)
// using "String" as default serializeAs...just in case, no real good reason.
if (settingElements != null)
{
@ -310,74 +324,74 @@ namespace SystemTrayMenu.Properties
SettingStruct newSetting = new()
{
Name = element.Attribute(NameOf) == null ? string.Empty : name,
SerializeAs = serializeAs == null ? "String" : serializeAs,
SerializeAs = serializeAs ?? "String",
Value = element.Value ?? string.Empty,
};
SettingsDictionary.Add(name, newSetting);
}
}
}
}
}
/// <summary>
/// Saves the in memory dictionary to the user config file.
/// </summary>
private void SaveValuesToFile()
{
// load the current xml from the file.
XDocument? configXml;
if (IsConfigPathAssembly())
{
configXml = LoadOrGetNew(ConfigPathAssembly!);
}
else
{
configXml = LoadOrGetNew(UserConfigPath);
}
if (configXml != null)
{
// get the settings group (e.g. <Company.Project.Desktop.Settings>)
XElement? settingsSection = configXml.Element(Config)?.Element(UserSettings)?.Element(typeof(Settings).FullName);
// iterate though the dictionary, either updating the value or adding the new setting.
foreach (KeyValuePair<string, SettingStruct> entry in SettingsDictionary)
{
XElement? setting = settingsSection?.Elements().FirstOrDefault(e => e.Attribute(NameOf)?.Value == entry.Key);
if (setting == null)
{
// this can happen if a new setting is added via the .settings designer.
XElement newSetting = new(Setting);
newSetting.Add(new XAttribute(NameOf, entry.Value.Name));
newSetting.Add(new XAttribute(SerializeAs, entry.Value.SerializeAs));
newSetting.Value = entry.Value.Value ?? string.Empty;
settingsSection?.Add(newSetting);
}
else
{
// update the value if it exists.
setting.Value = entry.Value.Value ?? string.Empty;
}
}
if (IsConfigPathAssembly())
{
configXml.Save(ConfigPathAssembly!);
}
configXml.Save(UserConfigPath);
}
}
/// <summary>
/// Helper struct.
/// </summary>
internal struct SettingStruct
{
internal string Name;
internal string SerializeAs;
internal string Value;
}
}
}
}
}
}
/// <summary>
/// Saves the in memory dictionary to the user config file.
/// </summary>
private void SaveValuesToFile()
{
// load the current xml from the file.
XDocument? configXml;
if (IsConfigPathAssembly())
{
configXml = LoadOrGetNew(ConfigPathAssembly!);
}
else
{
configXml = LoadOrGetNew(UserConfigPath);
}
if (configXml != null && !string.IsNullOrEmpty(SettingsFullTypeName))
{
// get the settings group (e.g. <Company.Project.Desktop.Settings>)
XElement? settingsSection = configXml.Element(Config)?.Element(UserSettings)?.Element(SettingsFullTypeName);
// iterate though the dictionary, either updating the value or adding the new setting.
foreach (KeyValuePair<string, SettingStruct> entry in SettingsDictionary)
{
XElement? setting = settingsSection?.Elements().FirstOrDefault(e => e.Attribute(NameOf)?.Value == entry.Key);
if (setting == null)
{
// this can happen if a new setting is added via the .settings designer.
XElement newSetting = new(Setting);
newSetting.Add(new XAttribute(NameOf, entry.Value.Name));
newSetting.Add(new XAttribute(SerializeAs, entry.Value.SerializeAs));
newSetting.Value = entry.Value.Value ?? string.Empty;
settingsSection?.Add(newSetting);
}
else
{
// update the value if it exists.
setting.Value = entry.Value.Value ?? string.Empty;
}
}
if (IsConfigPathAssembly())
{
configXml.Save(ConfigPathAssembly!);
}
configXml.Save(UserConfigPath);
}
}
/// <summary>
/// Helper struct.
/// </summary>
internal struct SettingStruct
{
internal string Name;
internal string SerializeAs;
internal string Value;
}
}
}

View file

@ -2,7 +2,7 @@
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
//
// Copyright (c) 2022-2022 Peter Kirmeier
// Copyright (c) 2022-2023 Peter Kirmeier
namespace SystemTrayMenu.UserInterface
{
@ -11,6 +11,7 @@ namespace SystemTrayMenu.UserInterface
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Windows;
@ -45,7 +46,7 @@ namespace SystemTrayMenu.UserInterface
{
if (imgstream != null)
{
BitmapImage imageSource = new BitmapImage();
BitmapImage imageSource = new ();
imageSource.BeginInit();
imageSource.StreamSource = imgstream;
imageSource.EndInit();
@ -170,16 +171,16 @@ namespace SystemTrayMenu.UserInterface
MoreRichTextBox.Visibility = Visibility.Visible;
MoreRichTextBox.Document.Blocks.Clear();
Paragraph para = new Paragraph();
Paragraph para = new ();
// Parse string to detect hyperlinks and add handlers to them
// See: https://mycsharp.de/forum/threads/97560/erledigt-dynamische-hyperlinks-in-wpf-flowdocument?page=1
int lastPos = 0;
foreach (Match match in RegexUrl.Matches(value))
foreach (Match match in RegexUrl.Matches(value).Cast<Match>())
{
if (match.Index != lastPos)
{
para.Inlines.Add(value.Substring(lastPos, match.Index - lastPos));
para.Inlines.Add(value[lastPos..match.Index]);
}
var link = new Hyperlink(new Run(match.Value))
@ -195,7 +196,7 @@ namespace SystemTrayMenu.UserInterface
if (lastPos < value.Length)
{
para.Inlines.Add(value.Substring(lastPos));
para.Inlines.Add(value[lastPos..]);
}
MoreRichTextBox.Document.Blocks.Add(para);
@ -432,7 +433,7 @@ namespace SystemTrayMenu.UserInterface
RegistryKey? rk = Registry.LocalMachine.OpenSubKey(keyName);
if (rk != null)
{
strSysInfoPath = (string)rk.GetValue(subKeyRef, string.Empty) !;
strSysInfoPath = (string)rk.GetValue(subKeyRef, string.Empty)!;
}
}
catch (Exception ex)
@ -662,7 +663,7 @@ namespace SystemTrayMenu.UserInterface
private void AboutBox_Load(object sender, RoutedEventArgs e)
{
// if the user didn't provide an assembly, try to guess which one is the entry assembly
AppEntryAssembly ??= Assembly.GetEntryAssembly() !;
AppEntryAssembly ??= Assembly.GetEntryAssembly()!;
AppEntryAssembly ??= Assembly.GetExecutingAssembly();
executingAssemblyName = Assembly.GetExecutingAssembly().GetName().Name;

View file

@ -23,18 +23,18 @@ namespace SystemTrayMenu.UserInterface.FolderBrowseDialog
/// <summary>
/// Gets or sets /sets folder in which dialog will be open.
/// </summary>
public string InitialFolder { get; set; }
public string? InitialFolder { get; set; }
/// <summary>
/// Gets or sets /sets directory in which dialog will be open
/// if there is no recent directory available.
/// </summary>
public string DefaultFolder { get; set; }
public string? DefaultFolder { get; set; }
/// <summary>
/// Gets or sets selected folder.
/// </summary>
public string Folder { get; set; }
public string? Folder { get; set; }
/// <summary>
/// Shows the file dialog and requests user interaction.

View file

@ -8,11 +8,11 @@ namespace SystemTrayMenu.UserInterface.FolderBrowseDialog
public interface IFolderDialog
{
string InitialFolder { get; set; }
string? InitialFolder { get; set; }
string DefaultFolder { get; set; }
string? DefaultFolder { get; set; }
string Folder { get; set; }
string? Folder { get; set; }
bool ShowDialog(Window owner);
}

View file

@ -34,6 +34,7 @@ namespace SystemTrayMenu.UserInterface
private static readonly RoutedEvent FadeOutEvent = EventManager.RegisterRoutedEvent(
nameof(FadeOut), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Menu));
private readonly DispatcherTimer timerUpdateIcons = new (DispatcherPriority.Render, Dispatcher.CurrentDispatcher);
private readonly string folderPath;
#if TODO // SEARCH
public const string RowFilterShowAll = "[SortIndex] LIKE '%0%'";
@ -46,7 +47,6 @@ namespace SystemTrayMenu.UserInterface
private bool isSetSearchText;
#endif
private bool isClosed = false; // TODO WPF Replace Forms wrapper
private DispatcherTimer timerUpdateIcons = new DispatcherTimer(DispatcherPriority.Render, Dispatcher.CurrentDispatcher);
internal Menu(MenuData menuData, string path)
{
@ -334,7 +334,7 @@ namespace SystemTrayMenu.UserInterface
public bool Disposing => isClosed; // TODO WPF Replace Forms wrapper
public System.Drawing.Point Location => new System.Drawing.Point((int)Left, (int)Top); // TODO WPF Replace Forms wrapper)
public System.Drawing.Point Location => new ((int)Left, (int)Top); // TODO WPF Replace Forms wrapper)
internal int Level { get; set; }
@ -716,7 +716,7 @@ namespace SystemTrayMenu.UserInterface
case StartLocation.Predecessor:
RowData? trigger = RowDataParent;
ListView dgv = menuPredecessor!.GetDataGridView() !;
ListView dgv = menuPredecessor!.GetDataGridView()!;
// Set position on same height as the selected row from predecessor
y = menuPredecessor.Location.Y;
@ -730,8 +730,7 @@ namespace SystemTrayMenu.UserInterface
// When scrolled, we have to reduce the index number as we calculate based on visual tree
int startIndex = 0;
double offset = 0D;
ScrollViewer? scrollViewer = (VisualTreeHelper.GetChild(dgv, 0) as Decorator)?.Child as ScrollViewer;
if (scrollViewer != null)
if (VisualTreeHelper.GetChild(dgv, 0) is Decorator { Child: ScrollViewer scrollViewer })
{
startIndex = (int)scrollViewer.VerticalOffset;
if (trigger.RowIndex < startIndex)

View file

@ -27,6 +27,7 @@ namespace SystemTrayMenu.UserInterface
private const string Command = @"Software\Classes\directory\shell\SystemTrayMenu_SetAsRootFolder\command";
private static SettingsWindow? settingsForm;
#if TODO // HOTKEY
private bool inHotkey;
#endif
@ -41,7 +42,7 @@ namespace SystemTrayMenu.UserInterface
{
if (imgstream != null)
{
BitmapImage imageSource = new BitmapImage();
BitmapImage imageSource = new();
imageSource.BeginInit();
imageSource.StreamSource = imgstream;
imageSource.EndInit();
@ -629,12 +630,9 @@ namespace SystemTrayMenu.UserInterface
{
RegistryKey? key = Registry.CurrentUser.OpenSubKey(
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true);
if (key != null)
{
key.SetValue(
key?.SetValue(
Assembly.GetExecutingAssembly().GetName().Name,
Environment.ProcessPath!);
}
Settings.Default.IsAutostartActivated = true;
}
@ -642,10 +640,7 @@ namespace SystemTrayMenu.UserInterface
{
RegistryKey? key = Registry.CurrentUser.OpenSubKey(
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true);
if (key != null)
{
key.DeleteValue("SystemTrayMenu", false);
}
key?.DeleteValue("SystemTrayMenu", false);
Settings.Default.IsAutostartActivated = false;
}
@ -945,8 +940,11 @@ namespace SystemTrayMenu.UserInterface
if (dialog.ShowDialog(this))
{
dataGridViewFolders.Items.Add(new ListViewItemData(dialog.Folder, false, true));
EnableButtonAddStartMenu();
if (!string.IsNullOrEmpty(dialog.Folder))
{
dataGridViewFolders.Items.Add(new ListViewItemData(dialog.Folder, false, true));
EnableButtonAddStartMenu();
}
}
dataGridViewFolders.SelectedItem = null;

View file

@ -735,7 +735,7 @@ namespace SystemTrayMenu.Utilities
try
{
if (arrPIDLs == null)
if (arrPIDLs == null || oParentFolder == null || oContextMenu == null)
{
ReleaseAll();
return;
@ -832,6 +832,14 @@ namespace SystemTrayMenu.Utilities
/// by calling HandleMenuMsg and HandleMenuMsg2. It will also call the OnContextMenuMouseHover
/// method of Browser when hovering over a ContextMenu item.
/// </summary>
/// <param name="hwnd">The window handle.</param>
/// <param name="msg">The message ID.</param>
/// <param name="wParam">The message's wParam value.</param>
/// <param name="lParam">The message's lParam value.</param>
/// <param name="handled">A value that indicates whether the message was handled.
/// Set the value to true if the message was handled; otherwise, false.</param>
/// <returns>The appropriate return value depends on the particular message.
/// See the message documentation details for the Win32 message being handled.</returns>
protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (oContextMenu2 != null &&
@ -867,7 +875,7 @@ namespace SystemTrayMenu.Utilities
/// </summary>
/// <param name="arrFI">Array of FileInfo.</param>
/// <returns>Array of PIDLs.</returns>
protected IntPtr[] GetPIDLs(FileInfo[] arrFI)
protected IntPtr[]? GetPIDLs(FileInfo[] arrFI)
{
if (arrFI == null || arrFI.Length == 0)
{
@ -907,14 +915,14 @@ namespace SystemTrayMenu.Utilities
/// </summary>
/// <param name="arrFI">Array of DirectoryInfo.</param>
/// <returns>Array of PIDLs.</returns>
protected IntPtr[] GetPIDLs(DirectoryInfo[] arrFI)
protected IntPtr[]? GetPIDLs(DirectoryInfo[] arrFI)
{
if (arrFI == null || arrFI.Length == 0 || arrFI[0].Parent == null)
{
return null;
}
IShellFolder? oParentFolder = GetParentFolder(arrFI[0].Parent.FullName);
IShellFolder? oParentFolder = GetParentFolder(arrFI[0].Parent?.FullName);
if (oParentFolder == null)
{
return null;
@ -942,7 +950,7 @@ namespace SystemTrayMenu.Utilities
return arrPIDLs;
}
private static void InvokeCommand(IContextMenu contextMenu, uint nCmd, string strFolder, Point pointInvoke)
private static void InvokeCommand(IContextMenu contextMenu, uint nCmd, string? strFolder, Point pointInvoke)
{
CMINVOKECOMMANDINFOEX invoke = new()
{
@ -1067,12 +1075,12 @@ namespace SystemTrayMenu.Utilities
/// </summary>
/// <param name="folderName">Folder path.</param>
/// <returns>IShellFolder for the folder (relative from the desktop).</returns>
private IShellFolder? GetParentFolder(string folderName)
private IShellFolder? GetParentFolder(string? folderName)
{
if (oParentFolder == null)
{
IShellFolder oDesktopFolder = GetDesktopFolder();
if (oDesktopFolder == null)
if (oDesktopFolder == null || folderName == null)
{
return null;
}
@ -1088,7 +1096,7 @@ namespace SystemTrayMenu.Utilities
IntPtr pStrRet = Marshal.AllocCoTaskMem((MaxPath * 2) + 4);
Marshal.WriteInt32(pStrRet, 0, 0);
_ = this.oDesktopFolder.GetDisplayNameOf(pPIDL, SHGNO.FORPARSING, pStrRet);
_ = oDesktopFolder.GetDisplayNameOf(pPIDL, SHGNO.FORPARSING, pStrRet);
StringBuilder strFolder = new(MaxPath);
_ = DllImports.NativeMethods.ShlwapiStrRetToBuf(pStrRet, pPIDL, strFolder, MaxPath);
Marshal.FreeCoTaskMem(pStrRet);
@ -1131,7 +1139,7 @@ namespace SystemTrayMenu.Utilities
[MarshalAs(UnmanagedType.LPStr)]
public string LpParameters;
[MarshalAs(UnmanagedType.LPStr)]
public string LpDirectory;
public string? LpDirectory;
public SW NShow;
public int DwHotKey;
public IntPtr HIcon;
@ -1141,7 +1149,7 @@ namespace SystemTrayMenu.Utilities
[MarshalAs(UnmanagedType.LPWStr)]
public string LpParametersW;
[MarshalAs(UnmanagedType.LPWStr)]
public string LpDirectoryW;
public string? LpDirectoryW;
[MarshalAs(UnmanagedType.LPWStr)]
public string LpTitleW;
public POINT PtInvoke;
@ -1150,7 +1158,7 @@ namespace SystemTrayMenu.Utilities
// Contains information about a menu item
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct MENUITEMINFO
private readonly struct MENUITEMINFO
{
private readonly int cbSize;
private readonly MIIM fMask;

View file

@ -12,7 +12,7 @@ namespace SystemTrayMenu.Utilities
internal class AppRestart
{
public static event Action BeforeRestarting;
public static event Action? BeforeRestarting;
internal static void ByThreadException()
{
@ -34,24 +34,31 @@ namespace SystemTrayMenu.Utilities
Restart(GetCurrentMethod());
}
private static void Restart(string reason)
private static void Restart(string? reason)
{
BeforeRestarting?.Invoke();
Log.Info($"Restart by '{reason}'");
Log.Info($"Restart by '{reason ?? "unkown"}'");
Log.Close();
using (Process p = new())
{
string fileName = System.Environment.ProcessPath;
p.StartInfo = new ProcessStartInfo(fileName);
try
string? fileName = System.Environment.ProcessPath;
if (string.IsNullOrEmpty(fileName))
{
p.Start();
Log.Warn("Restart failed", new());
}
catch (Win32Exception ex)
else
{
Log.Warn("Restart failed", ex);
p.StartInfo = new ProcessStartInfo(fileName);
try
{
p.Start();
}
catch (Win32Exception ex)
{
Log.Warn("Restart failed", ex);
}
}
}
@ -59,12 +66,12 @@ namespace SystemTrayMenu.Utilities
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static string GetCurrentMethod()
private static string? GetCurrentMethod()
{
StackTrace st = new();
StackFrame sf = st.GetFrame(1);
StackFrame? sf = st.GetFrame(1);
return sf.GetMethod().Name;
return sf?.GetMethod()?.Name;
}
}
}

View file

@ -22,7 +22,7 @@ namespace SystemTrayMenu.Utilities
else
{
Thread staThread = new(new ParameterizedThreadStart(StaThreadMethod));
void StaThreadMethod(object obj)
void StaThreadMethod(object? obj)
{
resolvedFilename = GetShortcutFileNamePath(shortcutFilename, out isFolderByShell);
}
@ -48,8 +48,8 @@ namespace SystemTrayMenu.Utilities
isFolder = false;
try
{
string pathOnly = Path.GetDirectoryName((string)shortcutFilename);
string filenameOnly = Path.GetFileName((string)shortcutFilename);
string? pathOnly = Path.GetDirectoryName((string)shortcutFilename);
string? filenameOnly = Path.GetFileName((string)shortcutFilename);
Shell shell = new();
Folder folder = shell.NameSpace(pathOnly);

View file

@ -1,90 +1,90 @@
// <copyright file="IconReader.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
// see also: https://www.codeproject.com/Articles/2532/Obtaining-and-managing-file-and-folder-icons-using.
namespace SystemTrayMenu.Utilities
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using SystemTrayMenu.DllImports;
/// <summary>
/// Provides static methods to read system icons for folders and files.
/// </summary>
public static class IconReader
{
private static readonly ConcurrentDictionary<string, Icon> DictIconCacheMainMenu = new();
private static readonly ConcurrentDictionary<string, Icon> DictIconCacheSubMenus = new();
public enum IconSize
{
Large = 0, // 32x32 pixels
Small = 1, // 16x16 pixels
}
public enum FolderType
{
Open = 0,
Closed = 1,
}
// see https://github.com/Hofknecht/SystemTrayMenu/issues/209.
public static bool MainPreload { get; set; }
public static void Dispose(bool includingMainMenu = true)
{
if (includingMainMenu)
{
foreach (Icon icon in DictIconCacheMainMenu.Values)
{
icon?.Dispose();
}
}
foreach (Icon icon in DictIconCacheSubMenus.Values)
{
icon?.Dispose();
}
}
public static bool ClearIfCacheTooBig()
{
bool cleared = false;
if (DictIconCacheSubMenus.Count > Properties.Settings.Default.ClearCacheIfMoreThanThisNumberOfItems)
{
Dispose(false);
DictIconCacheSubMenus.Clear();
cleared = true;
}
return cleared;
}
public static void RemoveIconFromCache(string path)
{
if (DictIconCacheMainMenu.Remove(path, out Icon? iconToRemove))
{
iconToRemove?.Dispose();
}
}
public static Icon? GetFileIconWithCache(
string? path,
string? resolvedPath,
bool linkOverlay,
bool updateIconInBackground,
bool isMainMenu,
out bool loading,
string keyPath = "")
{
loading = false;
// <copyright file="IconReader.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
// see also: https://www.codeproject.com/Articles/2532/Obtaining-and-managing-file-and-folder-icons-using.
namespace SystemTrayMenu.Utilities
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using SystemTrayMenu.DllImports;
/// <summary>
/// Provides static methods to read system icons for folders and files.
/// </summary>
public static class IconReader
{
private static readonly ConcurrentDictionary<string, Icon?> DictIconCacheMainMenu = new();
private static readonly ConcurrentDictionary<string, Icon?> DictIconCacheSubMenus = new();
public enum IconSize
{
Large = 0, // 32x32 pixels
Small = 1, // 16x16 pixels
}
public enum FolderType
{
Open = 0,
Closed = 1,
}
// see https://github.com/Hofknecht/SystemTrayMenu/issues/209.
public static bool MainPreload { get; set; }
public static void Dispose(bool includingMainMenu = true)
{
if (includingMainMenu)
{
foreach (Icon? icon in DictIconCacheMainMenu.Values)
{
icon?.Dispose();
}
}
foreach (Icon? icon in DictIconCacheSubMenus.Values)
{
icon?.Dispose();
}
}
public static bool ClearIfCacheTooBig()
{
bool cleared = false;
if (DictIconCacheSubMenus.Count > Properties.Settings.Default.ClearCacheIfMoreThanThisNumberOfItems)
{
Dispose(false);
DictIconCacheSubMenus.Clear();
cleared = true;
}
return cleared;
}
public static void RemoveIconFromCache(string path)
{
if (DictIconCacheMainMenu.Remove(path, out Icon? iconToRemove))
{
iconToRemove?.Dispose();
}
}
public static Icon? GetFileIconWithCache(
string? path,
string? resolvedPath,
bool linkOverlay,
bool updateIconInBackground,
bool isMainMenu,
out bool loading,
string keyPath = "")
{
loading = false;
Icon? icon = null;
if (path != null)
@ -107,7 +107,7 @@ namespace SystemTrayMenu.Utilities
if (IsExtensionWithSameIcon(extension))
{
key = extension + linkOverlay;
}
}
if (!DictIconCache(isMainMenu).TryGetValue(key, out icon) &&
!DictIconCache(!isMainMenu).TryGetValue(key, out icon))
@ -123,20 +123,20 @@ namespace SystemTrayMenu.Utilities
}
}
}
}
return icon;
}
public static Icon? GetFolderIconWithCache(
string? path,
bool linkOverlay,
bool updateIconInBackground,
bool isMainMenu,
out bool loading)
{
loading = false;
}
return icon;
}
public static Icon? GetFolderIconWithCache(
string? path,
bool linkOverlay,
bool updateIconInBackground,
bool isMainMenu,
out bool loading)
{
loading = false;
Icon? icon = null;
if (path != null)
{
@ -171,166 +171,166 @@ namespace SystemTrayMenu.Utilities
}
}
Icon GetFolder(string keyExtension)
Icon? GetFolder(string keyExtension)
{
return GetIconSTA(path, path, linkOverlay, size, true);
}
}
}
}
return icon;
}
public static Icon? GetIconSTA(string path, string resolvedPath, bool linkOverlay, IconSize size, bool isFolder)
{
Icon? icon = null;
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
{
icon = GetIcon(path, resolvedPath, linkOverlay, size, isFolder);
}
else
{
Thread staThread = new(new ParameterizedThreadStart(StaThreadMethod));
void StaThreadMethod(object? obj)
{
icon = GetIcon(path, resolvedPath, linkOverlay, size, isFolder);
}
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start(icon);
staThread.Join();
}
return icon;
}
public static Icon? AddIconOverlay(Icon? originalIcon, Icon overlay)
{
Icon? icon = originalIcon;
if (originalIcon != null)
{
using Bitmap target = new(originalIcon.Width, originalIcon.Height, PixelFormat.Format32bppArgb);
using Graphics graphics = Graphics.FromImage(target);
graphics.DrawIcon(originalIcon, 0, 0);
graphics.DrawIcon(overlay, new(0, 0, originalIcon.Width + 2, originalIcon.Height + 2));
target.MakeTransparent(target.GetPixel(1, 1));
IntPtr hIcon = target.GetHicon();
icon = (Icon)Icon.FromHandle(hIcon).Clone();
NativeMethods.User32DestroyIcon(hIcon);
}
return icon;
}
private static ConcurrentDictionary<string, Icon> DictIconCache(bool isMainMenu)
{
if (isMainMenu)
{
return DictIconCacheMainMenu;
}
else
{
return DictIconCacheSubMenus;
}
}
private static bool IsExtensionWithSameIcon(string fileExtension)
{
bool isExtensionWithSameIcon = true;
List<string> extensionsWithDiffIcons = new() { string.Empty, ".EXE", ".LNK", ".ICO", ".URL" };
if (extensionsWithDiffIcons.Contains(fileExtension.ToUpperInvariant()))
{
isExtensionWithSameIcon = false;
}
return isExtensionWithSameIcon;
}
private static Icon? GetIcon(string path, string? resolvedPath, bool linkOverlay, IconSize size, bool isFolder)
{
Icon? icon;
if (Path.GetExtension(path).Equals(".ico", StringComparison.InvariantCultureIgnoreCase))
{
icon = Icon.ExtractAssociatedIcon(path);
}
}
return icon;
}
public static Icon? GetIconSTA(string path, string? resolvedPath, bool linkOverlay, IconSize size, bool isFolder)
{
Icon? icon = null;
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
{
icon = GetIcon(path, resolvedPath, linkOverlay, size, isFolder);
}
else
{
Thread staThread = new(new ParameterizedThreadStart(StaThreadMethod));
void StaThreadMethod(object? obj)
{
icon = GetIcon(path, resolvedPath, linkOverlay, size, isFolder);
}
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start(icon);
staThread.Join();
}
return icon;
}
public static Icon? AddIconOverlay(Icon? originalIcon, Icon overlay)
{
Icon? icon = originalIcon;
if (originalIcon != null)
{
using Bitmap target = new(originalIcon.Width, originalIcon.Height, PixelFormat.Format32bppArgb);
using Graphics graphics = Graphics.FromImage(target);
graphics.DrawIcon(originalIcon, 0, 0);
graphics.DrawIcon(overlay, new(0, 0, originalIcon.Width + 2, originalIcon.Height + 2));
target.MakeTransparent(target.GetPixel(1, 1));
IntPtr hIcon = target.GetHicon();
icon = (Icon)Icon.FromHandle(hIcon).Clone();
NativeMethods.User32DestroyIcon(hIcon);
}
return icon;
}
private static ConcurrentDictionary<string, Icon?> DictIconCache(bool isMainMenu)
{
if (isMainMenu)
{
return DictIconCacheMainMenu;
}
else
{
return DictIconCacheSubMenus;
}
}
private static bool IsExtensionWithSameIcon(string fileExtension)
{
bool isExtensionWithSameIcon = true;
List<string> extensionsWithDiffIcons = new() { string.Empty, ".EXE", ".LNK", ".ICO", ".URL" };
if (extensionsWithDiffIcons.Contains(fileExtension.ToUpperInvariant()))
{
isExtensionWithSameIcon = false;
}
return isExtensionWithSameIcon;
}
private static Icon? GetIcon(string path, string? resolvedPath, bool linkOverlay, IconSize size, bool isFolder)
{
Icon? icon;
if (Path.GetExtension(path).Equals(".ico", StringComparison.InvariantCultureIgnoreCase))
{
icon = Icon.ExtractAssociatedIcon(path);
}
else if (File.Exists(resolvedPath) &&
Path.GetExtension(resolvedPath).Equals(".ico", StringComparison.InvariantCultureIgnoreCase))
{
icon = Icon.ExtractAssociatedIcon(resolvedPath);
if (linkOverlay)
{
icon = AddIconOverlay(icon, Properties.Resources.LinkArrow);
}
}
else
{
NativeMethods.SHFILEINFO shFileInfo = default;
uint flags = GetFlags(linkOverlay, size);
uint attribute = isFolder ? NativeMethods.FileAttributeDirectory : NativeMethods.FileAttributeNormal;
IntPtr imageList = NativeMethods.Shell32SHGetFileInfo(
path, attribute, ref shFileInfo, (uint)Marshal.SizeOf(shFileInfo), flags);
icon = GetIcon(path, linkOverlay, shFileInfo, imageList);
}
return icon;
}
private static uint GetFlags(bool linkOverlay, IconSize size)
{
uint flags = NativeMethods.ShgfiIcon | NativeMethods.ShgfiSYSICONINDEX;
if (linkOverlay)
{
flags += NativeMethods.ShgfiLINKOVERLAY;
}
if (size == IconSize.Small)
{
flags += NativeMethods.ShgfiSMALLICON;
}
else
{
flags += NativeMethods.ShgfiLARGEICON;
}
return flags;
}
private static Icon? GetIcon(
string path, bool linkOverlay, NativeMethods.SHFILEINFO shFileInfo, IntPtr imageList)
{
Icon? icon = null;
if (imageList != IntPtr.Zero)
{
IntPtr hIcon;
if (linkOverlay)
{
hIcon = shFileInfo.hIcon;
}
else
{
hIcon = NativeMethods.ImageList_GetIcon(
imageList, shFileInfo.iIcon, NativeMethods.IldTransparent);
}
try
{
icon = (Icon)Icon.FromHandle(hIcon).Clone();
}
catch (Exception ex)
{
Log.Warn($"path:'{path}'", ex);
}
if (!linkOverlay)
{
NativeMethods.User32DestroyIcon(hIcon);
}
NativeMethods.User32DestroyIcon(shFileInfo.hIcon);
}
return icon;
}
}
Path.GetExtension(resolvedPath).Equals(".ico", StringComparison.InvariantCultureIgnoreCase))
{
icon = Icon.ExtractAssociatedIcon(resolvedPath);
if (linkOverlay)
{
icon = AddIconOverlay(icon, Properties.Resources.LinkArrow);
}
}
else
{
NativeMethods.SHFILEINFO shFileInfo = default;
uint flags = GetFlags(linkOverlay, size);
uint attribute = isFolder ? NativeMethods.FileAttributeDirectory : NativeMethods.FileAttributeNormal;
IntPtr imageList = NativeMethods.Shell32SHGetFileInfo(
path, attribute, ref shFileInfo, (uint)Marshal.SizeOf(shFileInfo), flags);
icon = GetIcon(path, linkOverlay, shFileInfo, imageList);
}
return icon;
}
private static uint GetFlags(bool linkOverlay, IconSize size)
{
uint flags = NativeMethods.ShgfiIcon | NativeMethods.ShgfiSYSICONINDEX;
if (linkOverlay)
{
flags += NativeMethods.ShgfiLINKOVERLAY;
}
if (size == IconSize.Small)
{
flags += NativeMethods.ShgfiSMALLICON;
}
else
{
flags += NativeMethods.ShgfiLARGEICON;
}
return flags;
}
private static Icon? GetIcon(
string path, bool linkOverlay, NativeMethods.SHFILEINFO shFileInfo, IntPtr imageList)
{
Icon? icon = null;
if (imageList != IntPtr.Zero)
{
IntPtr hIcon;
if (linkOverlay)
{
hIcon = shFileInfo.hIcon;
}
else
{
hIcon = NativeMethods.ImageList_GetIcon(
imageList, shFileInfo.iIcon, NativeMethods.IldTransparent);
}
try
{
icon = (Icon)Icon.FromHandle(hIcon).Clone();
}
catch (Exception ex)
{
Log.Warn($"path:'{path}'", ex);
}
if (!linkOverlay)
{
NativeMethods.User32DestroyIcon(hIcon);
}
NativeMethods.User32DestroyIcon(shFileInfo.hIcon);
}
return icon;
}
}
}

View file

@ -15,25 +15,23 @@ namespace SystemTrayMenu.Utilities
{
private static bool hideHiddenEntries;
private static bool hideSystemEntries;
private static IShellDispatch4 iShellDispatch4;
private static IShellDispatch4? iShellDispatch4;
internal static void Initialize()
{
try
{
iShellDispatch4 = (IShellDispatch4)Activator.CreateInstance(
Type.GetTypeFromProgID("Shell.Application"));
iShellDispatch4 = (IShellDispatch4?)Activator.CreateInstance(
Type.GetTypeFromProgID("Shell.Application")!);
// Using SHGetSetSettings would be much better in performance but the results are not accurate.
// We have to go for the shell interface in order to receive the correct settings:
// https://docs.microsoft.com/en-us/windows/win32/shell/ishelldispatch4-getsetting
const int SSF_SHOWALLOBJECTS = 0x00000001;
hideHiddenEntries = !iShellDispatch4.GetSetting(
SSF_SHOWALLOBJECTS);
hideHiddenEntries = !(iShellDispatch4?.GetSetting(SSF_SHOWALLOBJECTS) ?? false);
const int SSF_SHOWSUPERHIDDEN = 0x00040000;
hideSystemEntries = !iShellDispatch4.GetSetting(
SSF_SHOWSUPERHIDDEN);
hideSystemEntries = !(iShellDispatch4?.GetSetting(SSF_SHOWSUPERHIDDEN) ?? false);
}
catch (Exception ex)
{
@ -59,7 +57,7 @@ namespace SystemTrayMenu.Utilities
internal static bool IsHidden(RowData rowData)
{
bool isDirectoryToHide = false;
if (rowData.Path.Length >= 260)
if (rowData.Path == null || rowData.Path.Length >= 260)
{
Log.Info($"path too long (>=260):'{rowData.Path}'");
return isDirectoryToHide;

View file

@ -5,13 +5,12 @@
namespace SystemTrayMenu.Utilities
{
using System;
using System.Drawing;
using System.Windows;
using System.Windows.Media;
internal static class Scaling
{
private static FontSizeConverter fontConverter = new FontSizeConverter();
private static readonly FontSizeConverter FontConverter = new ();
public static float Factor { get; private set; } = 1;
@ -34,7 +33,7 @@ namespace SystemTrayMenu.Utilities
public static double ScaleFontByPoints(float points)
{
return (double)fontConverter.ConvertFrom((points * Factor).ToString() + "pt") !;
return (double)FontConverter.ConvertFrom((points * Factor).ToString() + "pt")!;
}
public static double ScaleFontByPixels(float pixels)