I have created a small application which should get some data from internet trought Puppeteer Sharp, the problem's that after I instantiate the browser the software freeze without no error.
public partial class MainWindow : Window
{
public Handler Handler { get; } = new Handler();
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Handler.Init(Handler).Wait();
}
}
as you can see I have Handler which contains all the properties of the software:
public class Handler
{
private static Url URL = new Url("https://www.diretta.it/");
public static Browser Browser { get; set; }
public async Task<bool> Init(Handler prop)
{
DotNetEnv.Env.Load("./config.env");
// The problem's here
Browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
ExecutablePath = Environment.GetEnvironmentVariable("CHROME_PATH"),
});
return true;
}
}
where CHROME_PATH is this: CHROME_PATH="C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe"
what I did wrong? I have the latest version of Chrome and PuppeteerSharp too.
Change your Window_Loaded event method to async and do an await on the Init method, Event Handler methods are an exception. using async void instead of async Task is ok in this scenario. - Reference (Should I avoid 'async void' event handlers?):
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
await Handler.Init(Handler);
}
Related
I'm creating an application using Caliburn.Micro. The application communicates with an api during startup which is why I need to show a Splashscreen to the users. I've created my own animated Splashscreen as a Window which is activated from bootstrapper in the OnStartup method.
The startup process is managed by the splashscreens viewmodel.
When all startup related processes are finished how do I tell the bootstrapper to close the splashscreen and activate another window?
I thought about raising an event but I cannot subscribe the bootstrapper to the IEventaggregator.
I tried displaying the Splashscreen inside of a contentcontrol in the ShellView and just switch to a different vm after the loading is done. The problem here is that the splash should be displayed on a transparent, borderless window which cannot be changed after the window is created.
public class Bootstrapper : BootstrapperBase
{
private SimpleContainer _container = new SimpleContainer();
public Bootstrapper()
{
Initialize();
}
protected override void Configure()
{
_container
.Singleton<IWindowManager, WindowManager>()
.Singleton<IEventAggregator, EventAggregator>();
GetType().Assembly.GetTypes()
.Where(type => type.IsClass)
.Where(type => type.Name.EndsWith("ViewModel"))
.ToList()
.ForEach(viewModelType => _container.RegisterPerRequest(
viewModelType, viewModelType.ToString(), viewModelType));
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(
XmlLanguage.GetLanguage(
CultureInfo.CurrentCulture.IetfLanguageTag
)
)
);
DisplayRootViewFor<AnimatedSplashViewModel>();
//DisplayRootViewFor<ShellViewModel>();
}
protected override object GetInstance(Type service, string key)
{
return _container.GetInstance(service, key);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
}
public class AnimatedSplashViewModel : Screen
{
private IEventAggregator _events;
private string _splashMessage;
public string SplashMessage
{
get { return _splashMessage; }
set
{
_splashMessage = value;
NotifyOfPropertyChange(() => SplashMessage);
}
}
public AnimatedSplashViewModel(IEventAggregator events)
{
_events = events;
SplashMessage = "Please wait";
// Simulation of long tasks
var worker = new BackgroundWorker();
worker.DoWork += Worker_DoWork;
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
worker.RunWorkerAsync();
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_events.PublishOnUIThread(new SplashFinishedEvent());
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(10000);
}
}
You should either use a ShellViewModel for the root view and replace the splash screen view with a "main" view, or you could just wait to display the root view until the splash screen has been closed:
protected override void OnStartup(object sender, StartupEventArgs e)
{
Application.ShutdownMode = ShutdownMode.OnExplicitShutdown;
var windowManager = IoC.Get<IWindowManager>();
var eventAggregator = IoC.Get<IEventAggregator>();
windowManager.ShowDialog(new AnimatedSplashViewModel(eventAggregator));
DisplayRootViewFor(typeof(ShellViewModel));
}
...
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
TryClose();
}
I case anyone wondering about the final solution:
First I used the WindowManager to create a Dialog of the Splashscreen and let the SplashscreenViewModel do all the work.
Turned out this approach takes ages to load. So when I tried to execute it took around 8 seconds for the Dailog to show up. This is far too long for my impatient users.
I think this was because I used IoC to inject alot of dependecies into the SplashscreenViewModel.
windowManager.ShowDialog(new AnimatedSplashViewModel(locationEndpoint, userEndpoint, applicationEndpoint, adUser, clientInfo, locationInfo, loggedInUser));
Second approach was to create the Splashscreen as a dialog and use a BackgroundWorker for all the computing and api stuff inside the Bootstrapper.
While this worked quite fast I felt that there must be a better approach.
Third and final solution:
The Bootstrapper calls the ShellViewModel.
public Bootstrapper()
{
Initialize();
DisplayRootViewFor<ShellViewModel>();
}
In the OnInitialize method I've created a BackgroundWorker which executes all the long running tasks while displaying the SplashScreen as a Dialog using the WindowManager.
protected override void OnInitialize()
{
var windowManager = new WindowManager();
using (BackgroundWorker bw = new BackgroundWorker())
{
bw.DoWork += InitializeApplication;
bw.RunWorkerCompleted += InitializationCompleted;
bw.RunWorkerAsync();
windowManager.ShowDialog(new AnimatedSplashViewModel(_events));
}
}
The AnimatedSplashscreenViewModel now only requires one dependency which is the EventAggregator. I let it handle a custom Event named SplashMessageChangedEvent.
public class SplashMessageChangedEvent
{
public string Content { get; set; }
public bool CloseDialog { get; set; } = false;
public SplashMessageChangedEvent(string content)
{
Content = content;
}
public SplashMessageChangedEvent(bool closeDialog)
{
CloseDialog = closeDialog;
}
}
In the InitializationCompleted Event in the ShellViewModel I publish the following event to close the Dialog:
private void InitializationCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_events.PublishOnUIThread(new SplashMessageChangedEvent(true));
}
Now this final approach is much faster than the other two.
The Splashscreen is shown instantly after starting the executable.
I'm actually learning (the hard way) c# and been fighting for days with a problem :
I'm writing my first c# application with WPF (dotNet 4.0). When I click on a button, a BackgroundWorker thread is used and call a method from an external class, this way my UI don't freeze -> my method run as expected.
Then I tried to update a ListView control from thos external class to get some kind of progress (text) and I miserably failed.
I understand that I need to use a delegate and the dispatcher to update my control.
I tried to use the solution offered here How to update UI from another thread running in another class . (I cannot comment on it because of my low rep) and I miss some parts of the puzzle.
What the YourEventArgs(status) is referring to ? I just don't get the way to fire an event and pass the content back to my UI while my method is running inside the BGW.
So far I have this piece of code (Updated from answer):
namespace AppMain
{
public partial class MainWindow
{
BackgroundWorker AppWorker = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
AppWorker.WorkerSupportsCancellation = true;
AppWorker.DoWork += AppWorker_DoWork;
AppWorker.RunWorkerCompleted += AppWorker_RunWorkerCompleted;
}
private void btnLoad_Click(object sender, RoutedEventArgs e)
{
lstTest.Items.Add("Processing data...");
AppWorker.RunWorkerAsync();
}
public void AppWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
SetXmlData xml = new SetXmlData();
xml.ProgressUpdate += (s, evt) =>
{
Dispatcher.BeginInvoke((Action)(() =>
{
lstTest.Items.Add("this is a test : " + evt.myData); //how to retrieve the myData property from evt ?
}));
};
xml.FlushData();
}
public void AppWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
if (!(e.Cancelled))
{
lstTest.Items.Add("Done");
}
else
{
MessageBox.Show("Cancelled");
}
}
}
}
SetXmlData.cs
namespace AppMain
{
public class SetXmlData
{
public event EventHandler ProgressUpdate;
//update method
public void update(object input)
{
if (ProgressUpdate != null)
ProgressUpdate(this, new YourEventArgs { myData = (string)input });
}
//calculation method
public void FlushData()
{
MessageBox.Show("this is a test !");
update("test");
}
}
public class YourEventArgs : EventArgs
{
public string myData { get; set; }
}
}
Thanks for your help.
You can simply Invoke the ProgressUpdate event from the FlushData() method.
Simply call:
If (ProgressUpdate !=null )
{
ProgressUpdate(this,new YourEventArgs())
}
this is the source instance where the event originated from.
You could just create YourEventArgs by inheriting from EventArgs class.
public class YourEventArgs : EventArgs
{
//Put any property that you want to pass back to UI here.
}
When the event gets raised in the UI:
RaiseEvent.ProgressUpdate += (s, e) =>
{
Dispatcher.BeginInvoke((Action)(() =>
{
lstTest.Items.Add("this is a test : ");
//Add items to your UI control here...
}));
};
e will be of type YourEventArgs.
On a side note, you should never touch UI thread from a diffent thread (like background worker thread in your example). Since your event-handler already does the Dispatcher.BeginInvoke, that's safe.
Also, your ProgressUpdate event should be inside of your class SetXmlData.
try get;set; Example:
Form1:
public partial class Form1 : Form
{
static public string gettext { get; set; }
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Class1.send(); //call to class function
textBox1.Text = gettext; //items.add(gettext)
}
}
Class1:
class Class1
{
static public void send()
{
Form1.gettext = "Marko"; //Set gettext to string "Marko"
}
}
I have a form with a WebBrowser control, and an extra seperate thread that controls the browser and waits for it to load. Here's a code example:
private void Form1_Load(object sender, EventArgs e){
JobClass.mainAsync();
}
-
public static class JobClass
{
public static void mainAsync()
{
Thread t = new Thread(main);
t.Start();
}
private static void main()
{
Form1 frm = (Form1)Application.OpenForms["Form1"];
WebBrowser wb = frm.webBrowser1;
gotoGoogle(frm, wb);
}
private static void gotoGoogle(Form1 frm, WebBrowser wb)
{
frm.Invoke((MethodInvoker)delegate
{
wb.Navigate("google.com");
string loc = wb.Document.Url.AbsolutePath;
// ... some extra code ...
});
}
private static void gotoYoutube(Form1 frm, WebBrowser wb)
{
frm.Invoke((MethodInvoker)delegate
{
wb.Navigate("youtube.com");
wb.Document.Body.getElementById("...");
// ... some extra code ...
});
}
}
Everything runs fine but as you see, I have to pass the Form1 variable to each method that deals with the browser control, and I have to write frm.Invoke() in all of them, which makes my code less portable and more painful as the code gets bigger.
I was wondering if there's something I could do inside the "main()" method to make the WebBrowser maybe a child of the same thread so I don't have to keep invoking it from the form each time ? If not, how can I just get rid of this ugly invoke thing ?
you can do the following
define an event in your JobClass call it OnNavigationChanged
handle the event raise in your Form1
in the handled event method you can call wb.Navigate(url);
here a sample code
1- JobClass delegate
// define this delegate just above the JobClass
public delegate void JobClassNavigationEvent(string url);
2- define the event
public delegate void JobClassNavigationEvent(string url);
public static class JobClass
{
public static event JobClassNavigationEvent OnNavigationChanged;
private static BackgroundWorker worker;
public static void mainAsync()
{
worker = new BackgroundWorker();
worker.DoWork += (s, e) =>
{
main();
};
worker.RunWorkerAsync();
}
private static void main()
{
gotoGoogle();
}
private static void gotoGoogle()
{
if (OnNavigationChanged != null)
OnNavigationChanged.Invoke("google.com");
}
private static void gotoYoutube()
{
if (OnNavigationChanged != null)
OnNavigationChanged.Invoke("youtube.com");
}
}
3- in your form
private void Form1_Load(object sender, EventArgs e){
JobClass.OnNavigationChanged+=(url)=>{
webBrowser1.Navigate(url);
// other code come here
};
JobClass.mainAsync();
}
hope this will help you
I have a listbox with filenames. When the selected index is changed I load the file.
I want something like jQuery's HoverIntent that delays the action of loading the file for a short time so the user can use the down arrow and quickly cycle through the items in the list without the application trying to load each one. Thread.Sleep pauses the whole app so a user can't select another list item until the sleep completes, this is obviously not what I want.
This will work if your using WinForms, make a call to the InitTimer method in the Form constructor.
Load the file in the _timer_Tick event handler. To change the delay set the Interval property in InitTimer to another value.
private System.Windows.Forms.Timer _timer;
private void InitTimer()
{
_timer = new Timer { Interval = 500 };
_timer.Tick += _timer_Tick;
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
_timer.Stop();
_timer.Start();
}
private void _timer_Tick(object sender, EventArgs e)
{
_timer.Stop();
// TODO: Load file here
}
Use Threading to separate the loading from your GUI.
This should get you started:
public partial class MainWindow : Window
{
CancellationTokenSource cts;
bool loading;
private void SelectedIndexChanged(int index)
{
if (loading)
cts.Cancel();
cts = new CancellationTokenSource();
var loader = new Task.Delay(1000);
loader.ContinueWith(() => LoadFile(index))
.ContinueWith((x) => DisplayResult(x));
loader.Start();
}
private void DisplayResult(Task t)
{
// TODO: Invoke this Method to MainThread
if (!cts.IsCancellationRequested)
{
// Actually display this file
}
}
Could not test, as I'm still on .net 4 whereas Task.Delay() is .net 4.5
You may need to add another field in the form for the file content transfer from the tasks to the GUI.
Winforms:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private CancellationTokenSource _cancel;
private object _loadLock = new object();
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
lock (_loadLock)
{
handleCancellation();
var loader = new Task((chosenFileItemInListbox) =>
{
Thread.Sleep(1000);
LoadFile(chosenFileItemInListbox);
}, listBox1.SelectedItem, _cancel.Token);
}
}
private bool handleCancellation()
{
bool cancelled = false;
lock (_loadLock)
{
if (_cancel != null)
{
if (!_cancel.IsCancellationRequested)
{
_cancel.Cancel();
cancelled = true;
}
_cancel = null;
}
}
return cancelled;
}
private void LoadFile(object chosenFileItemInListbox)
{
if (handleCancellation())
{
return;
}
}
}
The code above could also be applied to WPF, but WPF contains some built in magic for handling delays and cancellation of previous updates.
<ListBox SelectedItem="{Binding Path=SelectedFile, Delay=1000}" />
After adding Service Reference to my Phone Application (for example http://www.deeptraining.com/webservices/weather.asmx?op=GetWeather), I tried to use AutoResetEvent for emulation syncronous method calling. But after calling WaitOne, method Set is never called. Why? Is it a bug?
public partial class MainPage : PhoneApplicationPage
{
private readonly AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
private string _result;
// Constructor
public MainPage()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
var weatherSoapClient = new WeatherSoapClient();
weatherSoapClient.GetWeatherCompleted += weatherSoapClient_GetWeatherCompleted;
weatherSoapClient.GetWeatherAsync("Pekin");
_autoResetEvent.WaitOne(); // Program stop hire
textBlock1.Text = _result;
}
void weatherSoapClient_GetWeatherCompleted(object sender, GetWeatherCompletedEventArgs e)
{
_result = e.Result;
_autoResetEvent.Set(); // Never invoke! Why???
}
}
In WP7, HTTP responses are processed on the UI thread. Bocking the UI thread prevents the response from being processed.