This commit is contained in:
Jack Ye 2026-03-10 23:09:15 +00:00 committed by GitHub
commit 8125bb3086
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 178 additions and 91 deletions

View file

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Plugin;
@ -99,14 +100,14 @@ namespace Flow.Launcher.Core.Plugin
{
case TextBox textBox:
var text = value as string ?? string.Empty;
textBox.Dispatcher.Invoke(() => textBox.Text = text);
DispatcherHelper.Invoke(() => textBox.Text = text);
break;
case PasswordBox passwordBox:
var password = value as string ?? string.Empty;
passwordBox.Dispatcher.Invoke(() => passwordBox.Password = password);
DispatcherHelper.Invoke(() => passwordBox.Password = password);
break;
case ComboBox comboBox:
comboBox.Dispatcher.Invoke(() => comboBox.SelectedItem = value);
DispatcherHelper.Invoke(() => comboBox.SelectedItem = value);
break;
case CheckBox checkBox:
var isChecked = value is bool boolValue
@ -114,7 +115,7 @@ namespace Flow.Launcher.Core.Plugin
// If can parse the default value to bool, use it, otherwise use false
: value is string stringValue && bool.TryParse(stringValue, out var boolValueFromString)
&& boolValueFromString;
checkBox.Dispatcher.Invoke(() => checkBox.IsChecked = isChecked);
DispatcherHelper.Invoke(() => checkBox.IsChecked = isChecked);
break;
}
}

View file

@ -0,0 +1,102 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace Flow.Launcher.Core.Resource;
#pragma warning disable VSTHRD001 // Avoid legacy thread switching APIs
public static class DispatcherHelper
{
public static void Invoke(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
Invoke(Application.Current?.Dispatcher, action, priority);
}
public static T Invoke<T>(Func<T> func, DispatcherPriority priority = DispatcherPriority.Normal)
{
return Invoke(Application.Current?.Dispatcher, func, priority);
}
public static void Invoke(Dispatcher dispatcher, Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
if (dispatcher == null) return;
if (dispatcher.CheckAccess())
{
action();
}
else
{
dispatcher.Invoke(action, priority);
}
}
public static T Invoke<T>(Dispatcher dispatcher, Func<T> func, DispatcherPriority priority = DispatcherPriority.Normal)
{
if (dispatcher == null) return default;
if (dispatcher.CheckAccess())
{
return func();
}
else
{
return dispatcher.Invoke(func, priority);
}
}
public static async Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
await InvokeAsync(Application.Current?.Dispatcher, action, priority);
}
public static async Task<T> InvokeAsync<T>(Func<T> func, DispatcherPriority priority = DispatcherPriority.Normal)
{
return await InvokeAsync(Application.Current?.Dispatcher, func, priority);
}
public static async Task InvokeAsync(Func<Task> func, DispatcherPriority priority = DispatcherPriority.Normal)
{
await InvokeAsync(Application.Current?.Dispatcher, func, priority);
}
public static async Task InvokeAsync(Dispatcher dispatcher, Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
if (dispatcher == null) return;
if (dispatcher.CheckAccess())
{
action();
}
else
{
await dispatcher.InvokeAsync(action, priority);
}
}
public static async Task<T> InvokeAsync<T>(Dispatcher dispatcher, Func<T> func, DispatcherPriority priority = DispatcherPriority.Normal)
{
if (dispatcher == null) return default;
if (dispatcher.CheckAccess())
{
return func();
}
else
{
return await dispatcher.InvokeAsync(func, priority);
}
}
public static async Task InvokeAsync(Dispatcher dispatcher, Func<Task> func, DispatcherPriority priority = DispatcherPriority.Normal)
{
if (dispatcher == null) return;
if (dispatcher.CheckAccess())
{
await func();
}
else
{
var task = await dispatcher.InvokeAsync(func, priority);
await task;
}
}
}

View file

@ -596,7 +596,7 @@ namespace Flow.Launcher.Core.Resource
/// </summary>
public async Task RefreshFrameAsync()
{
await Application.Current.Dispatcher.InvokeAsync(() =>
await DispatcherHelper.InvokeAsync(() =>
{
// Get the actual backdrop type and drop shadow effect settings
var (backdropType, useDropShadowEffect) = GetActualValue();
@ -623,7 +623,7 @@ namespace Flow.Launcher.Core.Resource
/// </summary>
public async Task SetBlurForWindowAsync()
{
await Application.Current.Dispatcher.InvokeAsync(() =>
await DispatcherHelper.InvokeAsync(() =>
{
// Get the actual backdrop type and drop shadow effect settings
var (backdropType, _) = GetActualValue();

View file

@ -334,7 +334,7 @@ namespace Flow.Launcher
var timer = new PeriodicTimer(TimeSpan.FromHours(5));
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
{
Current.Dispatcher.Invoke(() =>
DispatcherHelper.Invoke(() =>
{
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
pluginUpdateWindow.ShowDialog();
@ -345,7 +345,7 @@ namespace Flow.Launcher
// check updates on startup
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
{
Current.Dispatcher.Invoke(() =>
DispatcherHelper.Invoke(() =>
{
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
pluginUpdateWindow.ShowDialog();
@ -444,7 +444,7 @@ namespace Flow.Launcher
{
// Dispose needs to be called on the main Windows thread,
// since some resources owned by the thread need to be disposed.
_mainWindow?.Dispatcher.Invoke(_mainWindow.Dispose);
DispatcherHelper.Invoke(_mainWindow?.Dispatcher, _mainWindow.Dispose);
_mainVM?.Dispose();
DialogJump.Dispose();
_internationalization.Dispose();

View file

@ -3,6 +3,7 @@ using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Flow.Launcher.Core.Resource;
// http://blogs.microsoft.co.il/arik/2010/05/28/wpf-single-instance-application/
// modified to allow single instace restart
@ -100,7 +101,9 @@ namespace Flow.Launcher.Helper
await pipeServer.WaitForConnectionAsync();
// Do an asynchronous call to ActivateFirstInstance function
Application.Current?.Dispatcher.Invoke(ActivateFirstInstance);
#pragma warning disable VSTHRD103 // Call async methods when in an async method
DispatcherHelper.Invoke(ActivateFirstInstance);
#pragma warning restore VSTHRD103 // Call async methods when in an async method
// Disconect client
pipeServer.Disconnect();

View file

@ -3,9 +3,9 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure;
using Microsoft.Win32;
@ -22,11 +22,11 @@ public static class WallpaperPathRetrieval
public static Brush GetWallpaperBrush()
{
// Invoke the method on the UI thread
if (!Application.Current.Dispatcher.CheckAccess())
{
return Application.Current.Dispatcher.Invoke(GetWallpaperBrush);
}
return DispatcherHelper.Invoke(GetWallpaperBrushCore);
}
private static Brush GetWallpaperBrushCore()
{
try
{
var wallpaperPath = Win32Helper.GetWallpaperPath();

View file

@ -153,7 +153,7 @@ namespace Flow.Launcher
Localize.appUpdateButtonContent(),
() =>
{
Application.Current.Dispatcher.Invoke(() =>
DispatcherHelper.Invoke(() =>
{
var releaseNotesWindow = new ReleaseNotesWindow();
releaseNotesWindow.Show();
@ -225,7 +225,7 @@ namespace Flow.Launcher
{
case nameof(MainViewModel.MainWindowVisibilityStatus):
{
Dispatcher.Invoke(() =>
DispatcherHelper.Invoke(() =>
{
if (_viewModel.MainWindowVisibilityStatus)
{
@ -269,7 +269,7 @@ namespace Flow.Launcher
{
// QueryTextBox seems to be update with a DispatcherPriority as low as ContextIdle.
// To ensure QueryTextBox is up to date with QueryText from the View, we need to Dispatch with such a priority
Dispatcher.Invoke(() => QueryTextBox.CaretIndex = QueryTextBox.Text.Length);
DispatcherHelper.Invoke(() => QueryTextBox.CaretIndex = QueryTextBox.Text.Length);
_viewModel.QueryTextCursorMovedToEnd = false;
}
break;
@ -542,7 +542,7 @@ namespace Flow.Launcher
// Switch to Normal state
WindowState = WindowState.Normal;
Application.Current?.Dispatcher.Invoke(new Action(() =>
DispatcherHelper.Invoke(() =>
{
double normalWidth = Width;
double normalHeight = Height;
@ -555,7 +555,7 @@ namespace Flow.Launcher
{
DragMove();
}
}), DispatcherPriority.ApplicationIdle);
}, DispatcherPriority.ApplicationIdle);
}
else
{
@ -726,19 +726,8 @@ namespace Flow.Launcher
{
Win32Helper.RegisterSleepModeListener(() =>
{
if (Application.Current == null)
{
return;
}
// We must run InitSoundEffects on UI thread because MediaPlayer is a DispatcherObject
if (!Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.Invoke(InitSoundEffects);
return;
}
InitSoundEffects();
DispatcherHelper.Invoke(InitSoundEffects);
});
}
catch (Exception e)

View file

@ -3,6 +3,7 @@ using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure;
namespace Flow.Launcher
@ -29,11 +30,16 @@ namespace Flow.Launcher
MessageBoxImage icon = MessageBoxImage.None,
MessageBoxResult defaultResult = MessageBoxResult.OK)
{
if (!Application.Current.Dispatcher.CheckAccess())
{
return Application.Current.Dispatcher.Invoke(() => Show(messageBoxText, caption, button, icon, defaultResult));
}
return DispatcherHelper.Invoke(() => ShowCore(messageBoxText, caption, button, icon, defaultResult));
}
private static MessageBoxResult ShowCore(
string messageBoxText,
string caption = "",
MessageBoxButton button = MessageBoxButton.OK,
MessageBoxImage icon = MessageBoxImage.None,
MessageBoxResult defaultResult = MessageBoxResult.OK)
{
try
{
msgBox = new MessageBoxEx(button);

View file

@ -3,6 +3,7 @@ using System.IO;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Animation;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin.SharedModels;
@ -80,12 +81,12 @@ namespace Flow.Launcher
Show();
await Dispatcher.InvokeAsync(async () =>
await DispatcherHelper.InvokeAsync(async () =>
{
if (!closing)
{
closing = true;
await Dispatcher.InvokeAsync(fadeOutStoryboard.Begin);
fadeOutStoryboard.Begin();
}
});
}

View file

@ -3,6 +3,7 @@ using System.IO;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Animation;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin.SharedModels;
@ -82,12 +83,12 @@ namespace Flow.Launcher
Show();
await Dispatcher.InvokeAsync(async () =>
await DispatcherHelper.InvokeAsync(async () =>
{
if (!closing)
{
closing = true;
await Dispatcher.InvokeAsync(fadeOutStoryboard.Begin);
fadeOutStoryboard.Begin();
}
});
}

View file

@ -1,7 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Windows;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure;
using Microsoft.Toolkit.Uwp.Notifications;
@ -41,7 +41,7 @@ namespace Flow.Launcher
public static void Show(string title, string subTitle, string iconPath = null)
{
Application.Current.Dispatcher.Invoke(() =>
DispatcherHelper.Invoke(() =>
{
ShowInternal(title, subTitle, iconPath);
});
@ -91,7 +91,7 @@ namespace Flow.Launcher
public static void ShowWithButton(string title, string buttonText, Action buttonAction, string subTitle, string iconPath = null)
{
Application.Current.Dispatcher.Invoke(() =>
DispatcherHelper.Invoke(() =>
{
ShowInternalWithButton(title, buttonText, buttonAction, subTitle, iconPath);
});

View file

@ -2,6 +2,7 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Flow.Launcher.Core.Resource;
namespace Flow.Launcher
{
@ -22,19 +23,7 @@ namespace Flow.Launcher
ProgressBoxEx progressBox = null;
try
{
if (!Application.Current.Dispatcher.CheckAccess())
{
await Application.Current.Dispatcher.InvokeAsync(() =>
{
progressBox = new ProgressBoxEx(cancelProgress)
{
Title = caption
};
progressBox.TitleTextBlock.Text = caption;
progressBox.Show();
});
}
else
await DispatcherHelper.InvokeAsync(() =>
{
progressBox = new ProgressBoxEx(cancelProgress)
{
@ -42,7 +31,7 @@ namespace Flow.Launcher
};
progressBox.TitleTextBlock.Text = caption;
progressBox.Show();
}
});
await reportProgressAsync(progressBox.ReportProgress).ConfigureAwait(false);
}
@ -54,28 +43,20 @@ namespace Flow.Launcher
}
finally
{
if (!Application.Current.Dispatcher.CheckAccess())
{
await Application.Current.Dispatcher.InvokeAsync(() =>
{
progressBox?.Close();
});
}
else
await DispatcherHelper.InvokeAsync(() =>
{
progressBox?.Close();
}
});
}
}
private void ReportProgress(double progress)
{
if (!Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.Invoke(() => ReportProgress(progress));
return;
}
DispatcherHelper.Invoke(() => ReportProgressCore(progress));
}
private void ReportProgressCore(double progress)
{
if (progress < 0)
{
ProgressBar.Value = 0;

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
@ -141,7 +141,7 @@ namespace Flow.Launcher
public void OpenSettingDialog()
{
Application.Current.Dispatcher.Invoke(() =>
DispatcherHelper.Invoke(() =>
{
SettingWindow sw = SingletonWindowOpener.Open<SettingWindow>();
});

View file

@ -9,6 +9,7 @@ using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure.Http;
using iNKORE.UI.WPF.Modern;
@ -28,7 +29,7 @@ namespace Flow.Launcher
private void ThemeManager_ActualApplicationThemeChanged(ThemeManager sender, object args)
{
Application.Current.Dispatcher.Invoke(() =>
DispatcherHelper.Invoke(() =>
{
if (ThemeManager.Current.ActualApplicationTheme == ApplicationTheme.Light)
{
@ -124,7 +125,7 @@ namespace Flow.Launcher
{
var output = await GetReleaseNotesMarkdownAsync().ConfigureAwait(false);
Application.Current.Dispatcher.Invoke(() =>
DispatcherHelper.Invoke(() =>
{
RefreshProgressRing.Visibility = Visibility.Collapsed;
if (string.IsNullOrEmpty(output))

View file

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using System.Windows;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
@ -115,7 +116,7 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
{
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
{
Application.Current.Dispatcher.Invoke(() =>
DispatcherHelper.Invoke(() =>
{
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
pluginUpdateWindow.ShowDialog();

View file

@ -16,6 +16,7 @@ using System.Windows.Threading;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.DialogJump;
@ -763,12 +764,11 @@ namespace Flow.Launcher.ViewModel
public void ChangeQueryText(string queryText, bool isReQuery = false)
{
// Must check access so that we will not block the UI thread which causes window visibility issue
if (!Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.Invoke(() => ChangeQueryText(queryText, isReQuery));
return;
}
DispatcherHelper.Invoke(() => ChangeQueryTextCore(queryText, isReQuery));
}
private void ChangeQueryTextCore(string queryText, bool isReQuery = false)
{
if (QueryText != queryText)
{
// Change query text first
@ -795,12 +795,11 @@ namespace Flow.Launcher.ViewModel
private async Task ChangeQueryTextAsync(string queryText, bool isReQuery = false)
{
// Must check access so that we will not block the UI thread which causes window visibility issue
if (!Application.Current.Dispatcher.CheckAccess())
{
await Application.Current.Dispatcher.InvokeAsync(() => ChangeQueryTextAsync(queryText, isReQuery));
return;
}
await DispatcherHelper.InvokeAsync(() => ChangeQueryTextCoreAsync(queryText, isReQuery));
}
private async Task ChangeQueryTextCoreAsync(string queryText, bool isReQuery = false)
{
if (QueryText != queryText)
{
// Change query text first
@ -1951,10 +1950,12 @@ namespace Flow.Launcher.ViewModel
if (dialogWindowHandleChanged)
{
// Only update the position
Application.Current?.Dispatcher.Invoke(() =>
#pragma warning disable VSTHRD103 // Call async methods when in an async method
DispatcherHelper.Invoke(() =>
{
(Application.Current?.MainWindow as MainWindow)?.UpdatePosition();
});
#pragma warning restore VSTHRD103 // Call async methods when in an async method
_ = ResetWindowAsync();
}
@ -2049,7 +2050,7 @@ namespace Flow.Launcher.ViewModel
if (_previousMainWindowVisibilityStatus)
{
// Only update the position
Application.Current?.Dispatcher.Invoke(() =>
DispatcherHelper.Invoke(() =>
{
(Application.Current?.MainWindow as MainWindow)?.UpdatePosition();
});
@ -2112,7 +2113,7 @@ namespace Flow.Launcher.ViewModel
if (App.LoadingOrExiting) return;
// When application is exiting, the Application.Current will be null
Application.Current?.Dispatcher.Invoke(() =>
DispatcherHelper.Invoke(() =>
{
// When application is exiting, the Application.Current will be null
if (Application.Current?.MainWindow is MainWindow mainWindow)
@ -2194,7 +2195,7 @@ namespace Flow.Launcher.ViewModel
}
// When application is exiting, the Application.Current will be null
Application.Current?.Dispatcher.Invoke(() =>
DispatcherHelper.Invoke(() =>
{
// When application is exiting, the Application.Current will be null
if (Application.Current?.MainWindow is MainWindow mainWindow)
@ -2328,7 +2329,7 @@ namespace Flow.Launcher.ViewModel
public void FocusQueryTextBox()
{
// When application is exiting, the Application.Current will be null
Application.Current?.Dispatcher.Invoke(() =>
DispatcherHelper.Invoke(() =>
{
// When application is exiting, the Application.Current will be null
if (Application.Current?.MainWindow is MainWindow window)