I have followed https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/data/clipboard?view=net-maui-7.0 in my project, and I understand that:
Access to the clipboard must be done on the main user interface thread. For more information on how to invoke methods on the main user interface thread, see MainThread.
I cannot do Clipboard.Default.SetTextAsync(url)
without errors, and I can't understand where I've done wrong.
I would like to get this code below fixed so that I can my app in macOS without any issues.
You can also check the code in GitHub by pulling the following two projects:
My code is as below.
using HelpersLib;
using Microsoft.Extensions.Logging;
using ShareX.HelpersLib;
using ShareX.UploadersLib;
namespace UploaderX;
public partial class MainPage : ContentPage
{
int count = 0;
private FileSystemWatcher _watcher;
private string _watchDir;
private string _destDir;
public delegate void UrlReceivedEventHandler(string url);
public event UrlReceivedEventHandler UrlReceived;
public MainPage()
{
InitializeComponent();
string AppDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "UploaderX");
string AppSettingsDir = Path.Combine(AppDir, "Settings");
App.Settings = ApplicationConfig.Load(Path.Combine(AppSettingsDir, "ApplicationConfig.json"));
App.UploadersConfig = UploadersConfig.Load(Path.Combine(AppSettingsDir, "UploadersConfig.json"));
App.UploadersConfig.SupportDPAPIEncryption = false;
DebugHelper.Init(Path.Combine(AppDir, $"UploaderX-{DateTime.Now.ToString("yyyyMMdd")}-Log.txt"));
_watchDir = Directory.Exists(App.Settings.CustomScreenshotsPath2) ? App.Settings.CustomScreenshotsPath2 : Path.Combine(AppDir, "Watch Folder");
Helpers.CreateDirectoryFromDirectoryPath(_watchDir);
_destDir = _watchDir;
DebugHelper.Logger.WriteLine("Watch Dir: " + _watchDir);
DebugHelper.Logger.WriteLine("Destination Dir: " + _destDir);
_watcher = new FileSystemWatcher();
_watcher.Path = _watchDir;
_watcher.NotifyFilter = NotifyFilters.FileName;
_watcher.Created += OnCreated;
_watcher.EnableRaisingEvents = true;
this.UrlReceived += MainPage_UrlReceived;
}
private async void MainPage_UrlReceived(string url)
{
await Clipboard.Default.SetTextAsync(url);
}
private void OnUrlReceived(string url)
{
UrlReceived?.Invoke(url);
}
private async void OnCounterClicked(object sender, EventArgs e)
{
count++;
if (count == 1)
CounterBtn.Text = $"Clicked {count} time";
else
CounterBtn.Text = $"Clicked {count} times";
await Clipboard.Default.SetTextAsync(CounterBtn.Text);
}
async void OnCreated(object sender, FileSystemEventArgs e)
{
try
{
string fileName = new NameParser(NameParserType.FileName).Parse("%y%mo%d_%ra{10}") + Path.GetExtension(e.FullPath);
string destPath = Path.Combine(Path.Combine(Path.Combine(_destDir, DateTime.Now.ToString("yyyy")), DateTime.Now.ToString("yyyy-MM")), fileName);
FileHelpers.CreateDirectoryFromFilePath(destPath);
if (!Path.GetFileName(e.FullPath).StartsWith("."))
{
int successCount = 0;
long previousSize = -1;
await Helpers.WaitWhileAsync(() =>
{
if (!FileHelpers.IsFileLocked(e.FullPath))
{
long currentSize = FileHelpers.GetFileSize(e.FullPath);
if (currentSize > 0 && currentSize == previousSize)
{
successCount++;
}
previousSize = currentSize;
return successCount < 4;
}
previousSize = -1;
return true;
}, 250, 5000, () =>
{
File.Move(e.FullPath, destPath, overwrite: true);
}, 1000);
WorkerTask task = new WorkerTask(destPath);
UploadResult result = task.UploadFile();
DebugHelper.Logger.WriteLine(result.URL);
OnUrlReceived(result.URL);
}
}
catch (Exception ex)
{
DebugHelper.Logger.WriteLine(ex.Message);
}
}
}
Error once URL is generated:
You can wrap your call to SetTextAsync()
with MainThread.BeginInvokeOnMainThread()
like so to make sure it's invoked correctly:
private void MainPage_UrlReceived(string url)
{
MainThread.BeginInvokeOnMainThread(() =>
{
Clipboard.Default.SetTextAsync(url);
});
}
There is no need to await
the call to SetTextAsync()
, either, because you're calling it as a singular operation from within an event handler, which isn't awaitable.
I understand this is not the UI thread, but I fire an event which I believe should enable UI thread operations
Generally, there is no guarantee that event handlers of UI classes are called on the Main Thread.
You can find more information on when and how to invoke methods on the Main Thread in the official documentation: https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/appmodel/main-thread?view=net-maui-7.0