Refine Walkthrough
This commit is contained in:
parent
6c8464b650
commit
a75932d1f4
@ -19,6 +19,7 @@
|
|||||||
Height="30"
|
Height="30"
|
||||||
Padding="30,3,30,3"
|
Padding="30,3,30,3"
|
||||||
Content="Save"
|
Content="Save"
|
||||||
|
Name="saveBtn"
|
||||||
Click="SaveButton_Clicked" />
|
Click="SaveButton_Clicked" />
|
||||||
|
|
||||||
<TabControl Name="tabControl" Grid.Column="0">
|
<TabControl Name="tabControl" Grid.Column="0">
|
||||||
|
|||||||
@ -1,102 +1,110 @@
|
|||||||
using ApplicationServices;
|
using ApplicationServices;
|
||||||
using AudibleUtilities;
|
using AudibleUtilities;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Presenters;
|
||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Styling;
|
||||||
using Dinah.Core.StepRunner;
|
using Dinah.Core.StepRunner;
|
||||||
using LibationAvalonia.Dialogs;
|
using LibationAvalonia.Dialogs;
|
||||||
using LibationAvalonia.Views;
|
using LibationAvalonia.Views;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using static Avalonia.Threading.Dispatcher;
|
||||||
|
|
||||||
namespace LibationAvalonia
|
namespace LibationAvalonia
|
||||||
{
|
{
|
||||||
internal class Walkthrough
|
internal class Walkthrough
|
||||||
{
|
{
|
||||||
private static Dictionary<string, string> settingTabMessages = new()
|
private Dictionary<string, string> settingTabMessages = new()
|
||||||
{
|
{
|
||||||
{ "Important Settings", "Change where liberated books are stored."},
|
{ "Important Settings", "From here you can change where liberated books are stored and how detailed Libation's logs are.\r\n\r\nIf you experience a problem and need help, you'll be asked to provide your log file. In certain circumstances we may need you to reproduce the error with a higher level of logging detail."},
|
||||||
{ "Import Library", "Change how your library is scanned, imported, and liberated."},
|
{ "Import Library", "In this tab you can change how your library is scanned and imported into Libation, as well as automatic liberation."},
|
||||||
{ "Download/Decrypt", "Control how liberated files and folders are named and stored."},
|
{ "Download/Decrypt", "These settings allow you to control how liberated files and folders are named and stored.\r\nYou can customize the 'Naming Templates' to use any number of the audiobook's properties to build a customized file and folder naming format. Learn more about the syntax from the wiki at\r\n\r\nhttps://github.com/rmcrackan/Libation/blob/master/Documentation/NamingTemplates.md"},
|
||||||
{ "Audio File Settings", "Control how audio files are decrypted, including audio format and metadata handling."},
|
{ "Audio File Settings", "Control how audio files are decrypted, including audio format and metadata handling.\r\n\r\nIf you choose to split your audiobook into multiple files by chapter marker, you may edit the chapter file 'Naming Template' to control how each chapter file is named."},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static readonly IBrush FlashColor = Brushes.DodgerBlue;
|
||||||
private readonly MainWindow MainForm;
|
private readonly MainWindow MainForm;
|
||||||
private readonly AsyncStepSequence sequence = new();
|
private readonly AsyncStepSequence sequence = new();
|
||||||
public Walkthrough(MainWindow mainForm)
|
public Walkthrough(MainWindow mainForm)
|
||||||
{
|
{
|
||||||
MainForm = mainForm;
|
MainForm = mainForm;
|
||||||
sequence[nameof(ShowAccountDialog)] = ShowAccountDialog;
|
sequence[nameof(ShowAccountDialog)] = () => UIThread.InvokeAsync(ShowAccountDialog);
|
||||||
sequence[nameof(ShowSettingsDialog)] = ShowSettingsDialog;
|
sequence[nameof(ShowSettingsDialog)] = () => UIThread.InvokeAsync(ShowSettingsDialog);
|
||||||
sequence[nameof(ShowAccountScanning)] = ShowAccountScanning;
|
sequence[nameof(ShowAccountScanning)] = () => UIThread.InvokeAsync(ShowAccountScanning);
|
||||||
sequence[nameof(ShowSearching)] = ShowSearching;
|
sequence[nameof(ShowSearching)] = () => UIThread.InvokeAsync(ShowSearching);
|
||||||
sequence[nameof(ShowQuickFilters)] = ShowQuickFilters;
|
sequence[nameof(ShowQuickFilters)] = () => UIThread.InvokeAsync(ShowQuickFilters);
|
||||||
|
sequence[nameof(ShowTourComplete)] = () => UIThread.InvokeAsync(ShowTourComplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RunAsync() => await sequence.RunAsync();
|
public async Task RunAsync() => await sequence.RunAsync();
|
||||||
|
|
||||||
private async Task<bool> ShowAccountDialog()
|
private async Task<bool> ShowAccountDialog()
|
||||||
{
|
{
|
||||||
if (await OkCancelMessageBox("First, add your Audible account(s).", "Add Accounts") is not DialogResult.OK) return false;
|
if (!await ProceedMessageBox("First, add your Audible account(s).", "Add Accounts"))
|
||||||
|
return false;
|
||||||
|
|
||||||
await Task.Delay(750);
|
await Task.Delay(750);
|
||||||
await flashControlAsync(MainForm.settingsToolStripMenuItem);
|
await displayControlAsync(MainForm.settingsToolStripMenuItem);
|
||||||
await InvokeAsync(MainForm.settingsToolStripMenuItem.Open);
|
await displayControlAsync(MainForm.accountsToolStripMenuItem);
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
await flashControlAsync(MainForm.accountsToolStripMenuItem);
|
|
||||||
await InvokeAsync(() => MainForm.accountsToolStripMenuItem.IsSelected = true);
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
var accountSettings = await InvokeAsync(() => new AccountsDialog());
|
var accountSettings = new AccountsDialog();
|
||||||
accountSettings.Opened += async (_, _) => await MessageBox.Show(accountSettings, "Add your Audible account(s), then save.", "Add an Account");
|
accountSettings.Loaded += async (_, _) => await MessageBox.Show(accountSettings, "Add your Audible account(s), then save.", "Add an Account");
|
||||||
await InvokeAsync(() => accountSettings.ShowDialog(MainForm));
|
await accountSettings.ShowDialog(MainForm);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> ShowSettingsDialog()
|
private async Task<bool> ShowSettingsDialog()
|
||||||
{
|
{
|
||||||
if (await OkCancelMessageBox("Next, adjust Libation's settings", "Change Settings") is not DialogResult.OK) return false;
|
if (!await ProceedMessageBox("Next, adjust Libation's settings", "Change Settings"))
|
||||||
|
return false;
|
||||||
|
|
||||||
await Task.Delay(750);
|
await Task.Delay(750);
|
||||||
await flashControlAsync(MainForm.settingsToolStripMenuItem);
|
await displayControlAsync(MainForm.settingsToolStripMenuItem);
|
||||||
await InvokeAsync(MainForm.settingsToolStripMenuItem.Open);
|
await displayControlAsync(MainForm.basicSettingsToolStripMenuItem);
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
await flashControlAsync(MainForm.basicSettingsToolStripMenuItem);
|
var settingsDialog = await UIThread.InvokeAsync(() => new SettingsDialog());
|
||||||
await InvokeAsync(() => MainForm.basicSettingsToolStripMenuItem.IsSelected = true);
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
var settingsDialog = await InvokeAsync(() => new SettingsDialog());
|
|
||||||
|
|
||||||
var tabsToVisit = settingsDialog.tabControl.Items.OfType<TabItem>().ToList();
|
var tabsToVisit = settingsDialog.tabControl.Items.OfType<TabItem>().ToList();
|
||||||
|
|
||||||
foreach (var tab in tabsToVisit)
|
foreach (var tab in tabsToVisit)
|
||||||
tab.PropertyChanged += TabControl_PropertyChanged;
|
tab.PropertyChanged += TabControl_PropertyChanged;
|
||||||
|
|
||||||
|
settingsDialog.Loaded += SettingsDialog_Loaded;
|
||||||
settingsDialog.Closing += SettingsDialog_FormClosing;
|
settingsDialog.Closing += SettingsDialog_FormClosing;
|
||||||
|
settingsDialog.saveBtn.Content = "Next Tab";
|
||||||
|
|
||||||
await InvokeAsync(() => settingsDialog.ShowDialog(MainForm));
|
await settingsDialog.ShowDialog(MainForm);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
async Task ShowTabPageMessageBoxAsync(TabItem selectedTab)
|
||||||
|
{
|
||||||
|
tabsToVisit.Remove(selectedTab);
|
||||||
|
|
||||||
|
if (!selectedTab.IsVisible || !(selectedTab.Header is TextBlock header && settingTabMessages.ContainsKey(header.Text))) return;
|
||||||
|
|
||||||
|
if (tabsToVisit.Count == 0)
|
||||||
|
settingsDialog.saveBtn.Content = "Save";
|
||||||
|
|
||||||
|
await MessageBox.Show(settingsDialog, settingTabMessages[header.Text], header.Text + " Tab", MessageBoxButtons.OK);
|
||||||
|
|
||||||
|
settingTabMessages.Remove(header.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
async void SettingsDialog_Loaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
await ShowTabPageMessageBoxAsync(tabsToVisit[0]);
|
||||||
|
}
|
||||||
|
|
||||||
async void TabControl_PropertyChanged(object sender, Avalonia.AvaloniaPropertyChangedEventArgs e)
|
async void TabControl_PropertyChanged(object sender, Avalonia.AvaloniaPropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Property == TabItem.IsSelectedProperty)
|
if (e.Property == TabItem.IsSelectedProperty && settingsDialog.IsLoaded)
|
||||||
{
|
{
|
||||||
var selectedTab = sender as TabItem;
|
await ShowTabPageMessageBoxAsync(sender as TabItem);
|
||||||
|
|
||||||
tabsToVisit.Remove(selectedTab);
|
|
||||||
|
|
||||||
if (!selectedTab.IsVisible || !(selectedTab.Header is TextBlock header && settingTabMessages.ContainsKey(header.Text))) return;
|
|
||||||
|
|
||||||
await MessageBox.Show(settingsDialog, settingTabMessages[header.Text], header.Text + " Tab", MessageBoxButtons.OK);
|
|
||||||
|
|
||||||
settingTabMessages.Remove(header.Text);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,10 +112,7 @@ namespace LibationAvalonia
|
|||||||
{
|
{
|
||||||
if (tabsToVisit.Count > 0)
|
if (tabsToVisit.Count > 0)
|
||||||
{
|
{
|
||||||
var nextTab = tabsToVisit[0];
|
settingsDialog.tabControl.SelectedItem = tabsToVisit[0];
|
||||||
tabsToVisit.RemoveAt(0);
|
|
||||||
|
|
||||||
settingsDialog.tabControl.SelectedItem = nextTab;
|
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,32 +120,29 @@ namespace LibationAvalonia
|
|||||||
|
|
||||||
private async Task<bool> ShowAccountScanning()
|
private async Task<bool> ShowAccountScanning()
|
||||||
{
|
{
|
||||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||||
var count = persister.AccountsSettings.Accounts.Count;
|
var count = persister.AccountsSettings.Accounts.Count;
|
||||||
|
persister.Dispose();
|
||||||
|
|
||||||
if (count < 1)
|
if (count < 1)
|
||||||
{
|
{
|
||||||
await InvokeAsync(() => MessageBox.Show(MainForm, "Add an Audible account, then sync your library through the \"Import\" menu", "Add an Audible Account", MessageBoxButtons.OK, MessageBoxIcon.Information));
|
await MessageBox.Show(MainForm, "Add an Audible account, then sync your library through the 'Import' menu", "Add an Audible Account", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var accounts = count > 1 ? "accounts" : "account";
|
var accounts = count > 1 ? "accounts" : "account";
|
||||||
var library = count > 1 ? "libraries" : "library";
|
var library = count > 1 ? "libraries" : "library";
|
||||||
if (await OkCancelMessageBox($"Finally, scan your Audible {accounts} to sync your {library} with Libation", $"Scan {accounts}") is not DialogResult.OK) return false;
|
if(! await ProceedMessageBox($"Finally, scan your Audible {accounts} to sync your {library} with Libation.\r\n\r\nIf this is your first time scanning an account, you'll be prompted to enter your account's password to log into your Audible account.", $"Scan {accounts}"))
|
||||||
|
return false;
|
||||||
|
|
||||||
var scanItem = count > 1 ? MainForm.scanLibraryOfAllAccountsToolStripMenuItem : MainForm.scanLibraryToolStripMenuItem;
|
var scanItem = count > 1 ? MainForm.scanLibraryOfAllAccountsToolStripMenuItem : MainForm.scanLibraryToolStripMenuItem;
|
||||||
|
|
||||||
await Task.Delay(750);
|
await Task.Delay(750);
|
||||||
await flashControlAsync(MainForm.importToolStripMenuItem);
|
await displayControlAsync(MainForm.importToolStripMenuItem);
|
||||||
await InvokeAsync(MainForm.importToolStripMenuItem.Open);
|
await displayControlAsync(scanItem);
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
await flashControlAsync(scanItem);
|
scanItem.RaiseEvent(new RoutedEventArgs(MenuItem.ClickEvent));
|
||||||
await InvokeAsync(() => scanItem.IsSelected = true);
|
MainForm.importToolStripMenuItem.Close();
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
await InvokeAsync(() => scanItem.RaiseEvent(new RoutedEventArgs(MenuItem.ClickEvent)));
|
|
||||||
await InvokeAsync(MainForm.importToolStripMenuItem.Close);
|
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource();
|
var tcs = new TaskCompletionSource();
|
||||||
LibraryCommands.ScanEnd += LibraryCommands_ScanEnd;
|
LibraryCommands.ScanEnd += LibraryCommands_ScanEnd;
|
||||||
@ -169,88 +171,122 @@ namespace LibationAvalonia
|
|||||||
var firstAuthor = getFirstAuthor();
|
var firstAuthor = getFirstAuthor();
|
||||||
if (firstAuthor == null) return true;
|
if (firstAuthor == null) return true;
|
||||||
|
|
||||||
if (await OkCancelMessageBox("You can filter the grid entries by searching", "Searching") is not DialogResult.OK) return false;
|
if (!await ProceedMessageBox("You can filter the grid entries by searching", "Searching"))
|
||||||
|
return false;
|
||||||
|
|
||||||
await flashControlAsync(MainForm.filterSearchTb);
|
await displayControlAsync(MainForm.filterSearchTb);
|
||||||
|
|
||||||
await InvokeAsync(() => MainForm.filterSearchTb.Text = string.Empty);
|
MainForm.filterSearchTb.Text = string.Empty;
|
||||||
foreach (var c in firstAuthor)
|
foreach (var c in firstAuthor)
|
||||||
{
|
{
|
||||||
await InvokeAsync(() => MainForm.filterSearchTb.Text += c);
|
MainForm.filterSearchTb.Text += c;
|
||||||
await Task.Delay(200);
|
await Task.Delay(150);
|
||||||
}
|
}
|
||||||
|
|
||||||
await flashControlAsync(MainForm.filterBtn);
|
await displayControlAsync(MainForm.filterBtn);
|
||||||
await InvokeAsync(MainForm.filterBtn.Focus);
|
|
||||||
await Task.Delay(500);
|
MainForm.filterBtn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
|
||||||
|
|
||||||
await InvokeAsync(() => MainForm.filterBtn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)));
|
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
await MessageBox.Show(MainForm, "Libation provides a built-in cheat sheet for its query language", "Search Cheat Sheet");
|
await MessageBox.Show(MainForm, "Libation provides a built-in cheat sheet for its query language", "Search Cheat Sheet");
|
||||||
|
|
||||||
|
await displayControlAsync(MainForm.filterHelpBtn);
|
||||||
|
|
||||||
await flashControlAsync(MainForm.filterHelpBtn);
|
var filterHelp = new SearchSyntaxDialog();
|
||||||
var filterHelp = await InvokeAsync(() => new SearchSyntaxDialog());
|
await filterHelp.ShowDialog(MainForm);
|
||||||
await InvokeAsync(() => filterHelp.ShowDialog(MainForm));
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> ShowQuickFilters()
|
private async Task<bool> ShowQuickFilters()
|
||||||
{
|
{
|
||||||
var firstAuthor = getFirstAuthor();
|
var firstAuthor = getFirstAuthor();
|
||||||
|
|
||||||
if (firstAuthor == null) return true;
|
if (firstAuthor == null) return true;
|
||||||
|
|
||||||
if (await OkCancelMessageBox("Queries that you perform regularly can be added to 'Quick Filters'", "Quick Filters") is not DialogResult.OK) return false;
|
if (!await ProceedMessageBox("Queries that you perform regularly can be added to 'Quick Filters'", "Quick Filters"))
|
||||||
|
return false;
|
||||||
|
|
||||||
await InvokeAsync(() => MainForm.filterSearchTb.Text = firstAuthor);
|
MainForm.filterSearchTb.Text = firstAuthor;
|
||||||
|
|
||||||
await Task.Delay(750);
|
|
||||||
await flashControlAsync(MainForm.addQuickFilterBtn);
|
|
||||||
await InvokeAsync(() => MainForm.addQuickFilterBtn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)));
|
|
||||||
await Task.Delay(750);
|
|
||||||
|
|
||||||
await flashControlAsync(MainForm.quickFiltersToolStripMenuItem);
|
|
||||||
await InvokeAsync(MainForm.quickFiltersToolStripMenuItem.Open);
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
var editQuickFiltersToolStripMenuItem = MainForm.quickFiltersToolStripMenuItem.ItemsSource.OfType<MenuItem>().ElementAt(1);
|
var editQuickFiltersToolStripMenuItem = MainForm.quickFiltersToolStripMenuItem.ItemsSource.OfType<MenuItem>().ElementAt(1);
|
||||||
|
|
||||||
await flashControlAsync(editQuickFiltersToolStripMenuItem);
|
await Task.Delay(750);
|
||||||
await InvokeAsync(() => editQuickFiltersToolStripMenuItem.IsSelected = true);
|
await displayControlAsync(MainForm.addQuickFilterBtn);
|
||||||
await Task.Delay(500);
|
await displayControlAsync(MainForm.quickFiltersToolStripMenuItem);
|
||||||
|
await displayControlAsync(editQuickFiltersToolStripMenuItem);
|
||||||
|
|
||||||
var editQuickFilters = await InvokeAsync(() => new EditQuickFilters());
|
var editQuickFilters = new EditQuickFilters();
|
||||||
editQuickFilters.Opened += async (_, _) => await MessageBox.Show(editQuickFilters, "From here you can edit, delete, and change the order of Quick Filters", "Editing Quick Filters");
|
editQuickFilters.Loaded += async (_, _) => await MessageBox.Show(editQuickFilters, "From here you can edit, delete, and change the order of Quick Filters", "Editing Quick Filters");
|
||||||
await InvokeAsync(() => editQuickFilters.ShowDialog(MainForm));
|
await editQuickFilters.ShowDialog(MainForm);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<bool> ShowTourComplete()
|
||||||
|
{
|
||||||
|
await MessageBox.Show(MainForm, "You're now ready to begin using Libation.\r\n\r\nEnjoy!", "Tour Finished");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private string getFirstAuthor()
|
private string getFirstAuthor()
|
||||||
{
|
{
|
||||||
var books = DbContexts.GetLibrary_Flat_NoTracking();
|
var books = DbContexts.GetLibrary_Flat_NoTracking();
|
||||||
return books.SelectMany(lb => lb.Book.Authors).FirstOrDefault(a => !string.IsNullOrWhiteSpace(a.Name))?.Name;
|
return books.SelectMany(lb => lb.Book.Authors).FirstOrDefault(a => !string.IsNullOrWhiteSpace(a.Name))?.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task flashControlAsync(TemplatedControl control, int flashCount = 3)
|
private async Task displayControlAsync(TemplatedControl control)
|
||||||
|
{
|
||||||
|
await UIThread.InvokeAsync(() => control.IsEnabled = false);
|
||||||
|
await UIThread.InvokeAsync(MainForm.productsDisplay.Focus);
|
||||||
|
await UIThread.InvokeAsync(() => flashControlAsync(control));
|
||||||
|
if (control is MenuItem menuItem) await UIThread.InvokeAsync(menuItem.Open);
|
||||||
|
await Task.Delay(500);
|
||||||
|
await UIThread.InvokeAsync(() => control.IsEnabled = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task flashControlAsync(TemplatedControl control, int flashCount = 3)
|
||||||
{
|
{
|
||||||
var backColor = await InvokeAsync(() => control.Background);
|
|
||||||
for (int i = 0; i < flashCount; i++)
|
for (int i = 0; i < flashCount; i++)
|
||||||
{
|
{
|
||||||
await InvokeAsync(() => control.Background = Brushes.Firebrick);
|
control.Styles.Add(disabledStyle);
|
||||||
|
control.Styles.Add(disabledStyle2);
|
||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
await InvokeAsync(() => control.Background = backColor);
|
control.Styles.Remove(disabledStyle);
|
||||||
|
control.Styles.Remove(disabledStyle2);
|
||||||
|
control.Styles.Add(enabedStyle);
|
||||||
|
control.Styles.Add(enabedStyle2);
|
||||||
|
control.InvalidateVisual();
|
||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
|
control.Styles.Remove(enabedStyle);
|
||||||
|
control.Styles.Remove(enabedStyle2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<T> InvokeAsync<T>(Func<T> func) => Dispatcher.UIThread.InvokeAsync(func);
|
private async Task<bool> ProceedMessageBox(string message, string caption)
|
||||||
private Task<T> InvokeAsync<T>(Func<Task<T>> func) => Dispatcher.UIThread.InvokeAsync(func);
|
=> await MessageBox.Show(MainForm, message, caption, MessageBoxButtons.OKCancel) is DialogResult.OK;
|
||||||
private Task InvokeAsync(Func<Task> action) => Dispatcher.UIThread.InvokeAsync(action);
|
|
||||||
private Task InvokeAsync(Action action) => Dispatcher.UIThread.InvokeAsync(action);
|
|
||||||
|
|
||||||
private Task<DialogResult> OkCancelMessageBox(string message, string caption)
|
private static readonly Setter HighlightSetter = new Setter(Border.BackgroundProperty, FlashColor);
|
||||||
=> InvokeAsync(() => MessageBox.Show(MainForm, message, caption, MessageBoxButtons.OKCancel));
|
private static readonly Setter HighlightSetter2 = new Setter(ContentPresenter.BackgroundProperty, FlashColor);
|
||||||
|
private static readonly Setter TransparentSetter = new Setter(Border.BackgroundProperty, Brushes.Transparent);
|
||||||
|
private static readonly Setter TransparentSetter2 = new Setter(ContentPresenter.BackgroundProperty, Brushes.Transparent);
|
||||||
|
|
||||||
|
private static Selector TemplateSelector = Selectors.Is<TemplatedControl>(null).PropertyEquals(Avalonia.Input.InputElement.IsEnabledProperty, false).Template();
|
||||||
|
private static Selector ContentPresenterSelector = TemplateSelector.Is<ContentPresenter>();
|
||||||
|
private static Selector BorderSelector = TemplateSelector.Is<Border>();
|
||||||
|
|
||||||
|
private static readonly Style disabledStyle = new Style(_ => BorderSelector);
|
||||||
|
private static readonly Style disabledStyle2 = new Style(_ => ContentPresenterSelector);
|
||||||
|
private static readonly Style enabedStyle = new Style(_ => BorderSelector);
|
||||||
|
private static readonly Style enabedStyle2 = new Style(_ => ContentPresenterSelector);
|
||||||
|
|
||||||
|
static Walkthrough()
|
||||||
|
{
|
||||||
|
disabledStyle.Setters.Add(HighlightSetter);
|
||||||
|
disabledStyle2.Setters.Add(HighlightSetter2);
|
||||||
|
enabedStyle.Setters.Add(TransparentSetter);
|
||||||
|
enabedStyle2.Setters.Add(TransparentSetter2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1211,8 +1211,8 @@
|
|||||||
#endregion
|
#endregion
|
||||||
private System.Windows.Forms.Label booksLocationDescLbl;
|
private System.Windows.Forms.Label booksLocationDescLbl;
|
||||||
private System.Windows.Forms.Label inProgressDescLbl;
|
private System.Windows.Forms.Label inProgressDescLbl;
|
||||||
private System.Windows.Forms.Button saveBtn;
|
public System.Windows.Forms.Button saveBtn;
|
||||||
private System.Windows.Forms.Button cancelBtn;
|
public System.Windows.Forms.Button cancelBtn;
|
||||||
private System.Windows.Forms.CheckBox allowLibationFixupCbox;
|
private System.Windows.Forms.CheckBox allowLibationFixupCbox;
|
||||||
private DirectoryOrCustomSelectControl booksSelectControl;
|
private DirectoryOrCustomSelectControl booksSelectControl;
|
||||||
private DirectorySelectControl inProgressSelectControl;
|
private DirectorySelectControl inProgressSelectControl;
|
||||||
|
|||||||
@ -13,14 +13,15 @@ namespace LibationWinForms
|
|||||||
{
|
{
|
||||||
internal class Walkthrough
|
internal class Walkthrough
|
||||||
{
|
{
|
||||||
private static Dictionary<string, string> settingTabMessages = new()
|
private Dictionary<string, string> settingTabMessages = new()
|
||||||
{
|
{
|
||||||
{ "Important settings", "Change where liberated books are stored."},
|
{ "Important settings", "From here you can change where liberated books are stored and how detailed Libation's logs are.\r\n\r\nIf you experience a problem and need help, you'll be asked to provide your log file. In certain circumstances we may need you to reproduce the error with a higher level of logging detail."},
|
||||||
{ "Import library", "Change how your library is scanned, imported, and liberated."},
|
{ "Import library", "In this tab you can change how your library is scanned and imported into Libation, as well as automatic liberation."},
|
||||||
{ "Download/Decrypt", "Control how liberated files and folders are named and stored."},
|
{ "Download/Decrypt", "These settings allow you to control how liberated files and folders are named and stored.\r\nYou can customize the 'Naming Templates' to use any number of the audiobook's properties to build a customized file and folder naming format. Learn more about the syntax from the wiki at\r\n\r\nhttps://github.com/rmcrackan/Libation/blob/master/Documentation/NamingTemplates.md"},
|
||||||
{ "Audio File Options", "Control how audio files are decrypted, including audio format and metadata handling."},
|
{ "Audio File Options", "Control how audio files are decrypted, including audio format and metadata handling.\r\n\r\nIf you choose to split your audiobook into multiple files by chapter marker, you may edit the chapter file 'Naming Template' to control how each chapter file is named."},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static readonly Color FlashColor = Color.DodgerBlue;
|
||||||
private readonly Form1 MainForm;
|
private readonly Form1 MainForm;
|
||||||
private readonly AsyncStepSequence sequence = new();
|
private readonly AsyncStepSequence sequence = new();
|
||||||
public Walkthrough(Form1 form1)
|
public Walkthrough(Form1 form1)
|
||||||
@ -31,22 +32,19 @@ namespace LibationWinForms
|
|||||||
sequence[nameof(ShowAccountScanning)] = ShowAccountScanning;
|
sequence[nameof(ShowAccountScanning)] = ShowAccountScanning;
|
||||||
sequence[nameof(ShowSearching)] = ShowSearching;
|
sequence[nameof(ShowSearching)] = ShowSearching;
|
||||||
sequence[nameof(ShowQuickFilters)] = ShowQuickFilters;
|
sequence[nameof(ShowQuickFilters)] = ShowQuickFilters;
|
||||||
|
sequence[nameof(ShowTourComplete)] = ShowTourComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RunAsync() => await sequence.RunAsync();
|
public async Task RunAsync() => await sequence.RunAsync();
|
||||||
|
|
||||||
private async Task<bool> ShowAccountDialog()
|
private async Task<bool> ShowAccountDialog()
|
||||||
{
|
{
|
||||||
if (OkCancelMessageBox("First, add your Audible account(s).", "Add Accounts") is not DialogResult.OK) return false;
|
if (!ProceedMessageBox("First, add your Audible account(s).", "Add Accounts"))
|
||||||
|
return false;
|
||||||
|
|
||||||
await Task.Delay(750);
|
await Task.Delay(750);
|
||||||
await flashControlAsync(MainForm.settingsToolStripMenuItem);
|
await displayControlAsync(MainForm.settingsToolStripMenuItem);
|
||||||
MainForm.Invoke(MainForm.settingsToolStripMenuItem.ShowDropDown);
|
await displayControlAsync(MainForm.accountsToolStripMenuItem);
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
await flashControlAsync(MainForm.accountsToolStripMenuItem);
|
|
||||||
MainForm.Invoke(MainForm.accountsToolStripMenuItem.Select);
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
using var accountSettings = MainForm.Invoke(() => new AccountsDialog());
|
using var accountSettings = MainForm.Invoke(() => new AccountsDialog());
|
||||||
accountSettings.StartPosition = FormStartPosition.CenterParent;
|
accountSettings.StartPosition = FormStartPosition.CenterParent;
|
||||||
@ -57,16 +55,12 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
private async Task<bool> ShowSettingsDialog()
|
private async Task<bool> ShowSettingsDialog()
|
||||||
{
|
{
|
||||||
if (OkCancelMessageBox("Next, adjust Libation's settings", "Change Settings") is not DialogResult.OK) return false;
|
if (!ProceedMessageBox("Next, adjust Libation's settings", "Change Settings"))
|
||||||
|
return false;
|
||||||
|
|
||||||
await Task.Delay(750);
|
await Task.Delay(750);
|
||||||
await flashControlAsync(MainForm.settingsToolStripMenuItem);
|
await displayControlAsync(MainForm.settingsToolStripMenuItem);
|
||||||
MainForm.Invoke(MainForm.settingsToolStripMenuItem.ShowDropDown);
|
await displayControlAsync(MainForm.basicSettingsToolStripMenuItem);
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
await flashControlAsync(MainForm.basicSettingsToolStripMenuItem);
|
|
||||||
MainForm.Invoke(MainForm.basicSettingsToolStripMenuItem.Select);
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
using var settingsDialog = MainForm.Invoke(() => new SettingsDialog());
|
using var settingsDialog = MainForm.Invoke(() => new SettingsDialog());
|
||||||
|
|
||||||
@ -76,6 +70,8 @@ namespace LibationWinForms
|
|||||||
settingsDialog.FormClosing += SettingsDialog_FormClosing;
|
settingsDialog.FormClosing += SettingsDialog_FormClosing;
|
||||||
settingsDialog.Shown += TabControl_TabIndexChanged;
|
settingsDialog.Shown += TabControl_TabIndexChanged;
|
||||||
settingsDialog.tabControl.SelectedIndexChanged += TabControl_TabIndexChanged;
|
settingsDialog.tabControl.SelectedIndexChanged += TabControl_TabIndexChanged;
|
||||||
|
settingsDialog.cancelBtn.Text = "Next Tab";
|
||||||
|
settingsDialog.saveBtn.Visible = false;
|
||||||
|
|
||||||
MainForm.Invoke(() => settingsDialog.ShowDialog(MainForm));
|
MainForm.Invoke(() => settingsDialog.ShowDialog(MainForm));
|
||||||
|
|
||||||
@ -87,6 +83,12 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
tabsToVisit.Remove(selectedTab);
|
tabsToVisit.Remove(selectedTab);
|
||||||
|
|
||||||
|
if (tabsToVisit.Count == 0)
|
||||||
|
{
|
||||||
|
settingsDialog.cancelBtn.Text = "Cancel";
|
||||||
|
settingsDialog.saveBtn.Visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!selectedTab.Visible || !settingTabMessages.ContainsKey(selectedTab.Text)) return;
|
if (!selectedTab.Visible || !settingTabMessages.ContainsKey(selectedTab.Text)) return;
|
||||||
|
|
||||||
MessageBox.Show(selectedTab, settingTabMessages[selectedTab.Text], selectedTab.Text + " Tab", MessageBoxButtons.OK);
|
MessageBox.Show(selectedTab, settingTabMessages[selectedTab.Text], selectedTab.Text + " Tab", MessageBoxButtons.OK);
|
||||||
@ -98,10 +100,7 @@ namespace LibationWinForms
|
|||||||
{
|
{
|
||||||
if (tabsToVisit.Count > 0)
|
if (tabsToVisit.Count > 0)
|
||||||
{
|
{
|
||||||
var nextTab = tabsToVisit[0];
|
settingsDialog.tabControl.SelectedTab = tabsToVisit[0];
|
||||||
tabsToVisit.RemoveAt(0);
|
|
||||||
|
|
||||||
settingsDialog.tabControl.SelectedTab = nextTab;
|
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,29 +108,26 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
private async Task<bool> ShowAccountScanning()
|
private async Task<bool> ShowAccountScanning()
|
||||||
{
|
{
|
||||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||||
var count = persister.AccountsSettings.Accounts.Count;
|
var count = persister.AccountsSettings.Accounts.Count;
|
||||||
|
persister.Dispose();
|
||||||
|
|
||||||
if (count < 1)
|
if (count < 1)
|
||||||
{
|
{
|
||||||
MainForm.Invoke(() => MessageBox.Show(MainForm, "Add an Audible account, then sync your library through the \"Import\" menu", "Add an Audible Account", MessageBoxButtons.OK, MessageBoxIcon.Information));
|
MainForm.Invoke(() => MessageBox.Show(MainForm, "Add an Audible account, then sync your library through the 'Import' menu.", "Add an Audible Account", MessageBoxButtons.OK, MessageBoxIcon.Information));
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var accounts = count > 1 ? "accounts" :"account";
|
var accounts = count > 1 ? "accounts" :"account";
|
||||||
var library = count > 1 ? "libraries" : "library";
|
var library = count > 1 ? "libraries" : "library";
|
||||||
if (OkCancelMessageBox($"Finally, scan your Audible {accounts} to sync your {library} with Libation", $"Scan {accounts}") is not DialogResult.OK) return false;
|
if (!ProceedMessageBox($"Finally, scan your Audible {accounts} to sync your {library} with Libation.\r\n\r\nIf this is your first time scanning an account, you'll be prompted to enter your account's password to log into your Audible account.", $"Scan {accounts}"))
|
||||||
|
return false;
|
||||||
|
|
||||||
var scanItem = count > 1 ? MainForm.scanLibraryOfAllAccountsToolStripMenuItem : MainForm.scanLibraryToolStripMenuItem;
|
var scanItem = count > 1 ? MainForm.scanLibraryOfAllAccountsToolStripMenuItem : MainForm.scanLibraryToolStripMenuItem;
|
||||||
|
|
||||||
await Task.Delay(750);
|
await Task.Delay(750);
|
||||||
await flashControlAsync(MainForm.importToolStripMenuItem);
|
await displayControlAsync(MainForm.importToolStripMenuItem);
|
||||||
MainForm.Invoke(MainForm.importToolStripMenuItem.ShowDropDown);
|
await displayControlAsync(scanItem);
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
await flashControlAsync(scanItem);
|
|
||||||
MainForm.Invoke(scanItem.Select);
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
MainForm.Invoke(scanItem.PerformClick);
|
MainForm.Invoke(scanItem.PerformClick);
|
||||||
|
|
||||||
@ -162,28 +158,28 @@ namespace LibationWinForms
|
|||||||
var firstAuthor = getFirstAuthor();
|
var firstAuthor = getFirstAuthor();
|
||||||
if (firstAuthor == null) return true;
|
if (firstAuthor == null) return true;
|
||||||
|
|
||||||
if (OkCancelMessageBox("You can filter the grid entries by searching", "Searching") is not DialogResult.OK) return false;
|
if (!ProceedMessageBox("You can filter the grid entries by searching", "Searching"))
|
||||||
|
return false;
|
||||||
|
|
||||||
MainForm.Invoke(MainForm.filterSearchTb.Focus);
|
await displayControlAsync(MainForm.filterSearchTb);
|
||||||
await flashControlAsync(MainForm.filterSearchTb);
|
|
||||||
|
|
||||||
MainForm.Invoke(() => MainForm.filterSearchTb.Text = string.Empty);
|
MainForm.Invoke(() => MainForm.filterSearchTb.Text = string.Empty);
|
||||||
foreach (var c in firstAuthor)
|
foreach (var c in firstAuthor)
|
||||||
{
|
{
|
||||||
MainForm.Invoke(() => MainForm.filterSearchTb.Text += c);
|
MainForm.Invoke(() => MainForm.filterSearchTb.Text += c);
|
||||||
await Task.Delay(200);
|
await Task.Delay(150);
|
||||||
}
|
}
|
||||||
|
|
||||||
await flashControlAsync(MainForm.filterBtn);
|
await displayControlAsync(MainForm.filterBtn);
|
||||||
MainForm.Invoke(MainForm.filterBtn.Select);
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
MainForm.Invoke(MainForm.filterBtn.PerformClick);
|
MainForm.Invoke(MainForm.filterBtn.PerformClick);
|
||||||
|
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
MessageBox.Show(MainForm, "Libation provides a built-in cheat sheet for its query language", "Search Cheat Sheet");
|
MessageBox.Show(MainForm, "Libation provides a built-in cheat sheet for its query language", "Search Cheat Sheet");
|
||||||
|
|
||||||
await flashControlAsync(MainForm.filterHelpBtn);
|
await displayControlAsync(MainForm.filterHelpBtn);
|
||||||
|
|
||||||
using var filterHelp = MainForm.Invoke(() => new SearchSyntaxDialog());
|
using var filterHelp = MainForm.Invoke(() => new SearchSyntaxDialog());
|
||||||
MainForm.Invoke(filterHelp.ShowDialog);
|
MainForm.Invoke(filterHelp.ShowDialog);
|
||||||
|
|
||||||
@ -193,25 +189,17 @@ namespace LibationWinForms
|
|||||||
private async Task<bool> ShowQuickFilters()
|
private async Task<bool> ShowQuickFilters()
|
||||||
{
|
{
|
||||||
var firstAuthor = getFirstAuthor();
|
var firstAuthor = getFirstAuthor();
|
||||||
|
|
||||||
if (firstAuthor == null) return true;
|
if (firstAuthor == null) return true;
|
||||||
|
|
||||||
if (OkCancelMessageBox("Queries that you perform regularly can be added to 'Quick Filters'", "Quick Filters") is not DialogResult.OK) return false;
|
if (!ProceedMessageBox("Queries that you perform regularly can be added to 'Quick Filters'", "Quick Filters"))
|
||||||
|
return false;
|
||||||
|
|
||||||
MainForm.Invoke(() => MainForm.filterSearchTb.Text = firstAuthor);
|
MainForm.Invoke(() => MainForm.filterSearchTb.Text = firstAuthor);
|
||||||
|
|
||||||
await Task.Delay(750);
|
await Task.Delay(750);
|
||||||
await flashControlAsync(MainForm.addQuickFilterBtn);
|
await displayControlAsync(MainForm.addQuickFilterBtn);
|
||||||
MainForm.Invoke(MainForm.addQuickFilterBtn.PerformClick);
|
await displayControlAsync(MainForm.quickFiltersToolStripMenuItem);
|
||||||
await Task.Delay(750);
|
await displayControlAsync(MainForm.editQuickFiltersToolStripMenuItem);
|
||||||
|
|
||||||
await flashControlAsync(MainForm.quickFiltersToolStripMenuItem);
|
|
||||||
MainForm.Invoke(MainForm.quickFiltersToolStripMenuItem.ShowDropDown);
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
await flashControlAsync(MainForm.editQuickFiltersToolStripMenuItem);
|
|
||||||
MainForm.Invoke(MainForm.editQuickFiltersToolStripMenuItem.Select);
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
var editQuickFilters = MainForm.Invoke(() => new EditQuickFilters());
|
var editQuickFilters = MainForm.Invoke(() => new EditQuickFilters());
|
||||||
editQuickFilters.Shown += (_, _) => MessageBox.Show(editQuickFilters, "From here you can edit, delete, and change the order of Quick Filters", "Editing Quick Filters");
|
editQuickFilters.Shown += (_, _) => MessageBox.Show(editQuickFilters, "From here you can edit, delete, and change the order of Quick Filters", "Editing Quick Filters");
|
||||||
@ -220,18 +208,43 @@ namespace LibationWinForms
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Task<bool> ShowTourComplete()
|
||||||
|
{
|
||||||
|
MessageBox.Show(MainForm, "You're now ready to begin using Libation.\r\n\r\nEnjoy!", "Tour Finished");
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
private string getFirstAuthor()
|
private string getFirstAuthor()
|
||||||
{
|
{
|
||||||
var books = DbContexts.GetLibrary_Flat_NoTracking();
|
var books = DbContexts.GetLibrary_Flat_NoTracking();
|
||||||
return books.SelectMany(lb => lb.Book.Authors).FirstOrDefault(a => !string.IsNullOrWhiteSpace(a.Name))?.Name;
|
return books.SelectMany(lb => lb.Book.Authors).FirstOrDefault(a => !string.IsNullOrWhiteSpace(a.Name))?.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task displayControlAsync(ToolStripMenuItem menuItem)
|
||||||
|
{
|
||||||
|
MainForm.Invoke(() => menuItem.Enabled = false);
|
||||||
|
MainForm.Invoke(MainForm.productsDisplay.Focus);
|
||||||
|
await flashControlAsync(menuItem);
|
||||||
|
MainForm.Invoke(menuItem.ShowDropDown);
|
||||||
|
await Task.Delay(500);
|
||||||
|
MainForm.Invoke(() => menuItem.Enabled = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task displayControlAsync(Control button)
|
||||||
|
{
|
||||||
|
MainForm.Invoke(() => button.Enabled = false);
|
||||||
|
MainForm.Invoke(MainForm.productsDisplay.Focus);
|
||||||
|
await flashControlAsync(button);
|
||||||
|
await Task.Delay(500);
|
||||||
|
MainForm.Invoke(() => button.Enabled = true);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task flashControlAsync(Control control, int flashCount = 3)
|
private async Task flashControlAsync(Control control, int flashCount = 3)
|
||||||
{
|
{
|
||||||
var backColor = MainForm.Invoke(() => control.BackColor);
|
var backColor = MainForm.Invoke(() => control.BackColor);
|
||||||
for (int i = 0; i < flashCount; i++)
|
for (int i = 0; i < flashCount; i++)
|
||||||
{
|
{
|
||||||
MainForm.Invoke(() => control.BackColor = Color.Firebrick);
|
MainForm.Invoke(() => control.BackColor = FlashColor);
|
||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
MainForm.Invoke(() => control.BackColor = backColor);
|
MainForm.Invoke(() => control.BackColor = backColor);
|
||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
@ -243,14 +256,14 @@ namespace LibationWinForms
|
|||||||
var backColor = MainForm.Invoke(() => control.BackColor);
|
var backColor = MainForm.Invoke(() => control.BackColor);
|
||||||
for (int i = 0; i < flashCount; i++)
|
for (int i = 0; i < flashCount; i++)
|
||||||
{
|
{
|
||||||
MainForm.Invoke(() => control.BackColor = Color.Firebrick);
|
MainForm.Invoke(() => control.BackColor = FlashColor);
|
||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
MainForm.Invoke(() => control.BackColor = backColor);
|
MainForm.Invoke(() => control.BackColor = backColor);
|
||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DialogResult OkCancelMessageBox(string message, string caption)
|
private bool ProceedMessageBox(string message, string caption)
|
||||||
=> MainForm.Invoke(() => MessageBox.Show(MainForm, message, caption, MessageBoxButtons.OKCancel));
|
=> MainForm.Invoke(() => MessageBox.Show(MainForm, message, caption, MessageBoxButtons.OKCancel)) is DialogResult.OK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user