WebBrowser control from Console app using F# - wpf

I have a .netcoreapp3.1 Console App
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<UseWpf>true</UseWpf>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
</Project>
In Program.fs, I am instantiating a WebBrowser control and handling the DocumentCompleted event
let run() =
let uri = "https://www.microsoft.com"
let browser = new WebBrowser()
browser.DocumentCompleted.Add(fun _ -> handlePage browser uris)
browser.Navigate(uri)
[<STAThread>]
[<EntryPoint>]
let main argv =
run()
Console.ReadKey() |> ignore
0
When I run it, the DocumentCompleted event is never fired or handled - the program runs through to the end.
Am I missing?
Thanks in advance.

Am I missing?
The message pump. You need a Dispatcher to run a WPF app.
On another note, you're using the WinForms WebBrowser, that one exposes the DocumentCompleted event. The WPF variant has the LoadCompleted event, ref this post.
In WPF, however, the concept of Loaded is related to the visual tree. As you're not rendering the control, the event will never be raised. If we instead use the Navigated event, we can get there with minimal fuss.
open System
open System.Windows
open System.Windows.Controls
type BrowserApplication() =
inherit Application()
let run() =
let uri = "https://www.microsoft.com"
let browser = new WebBrowser()
browser.Navigated.Add(fun _ -> Console.WriteLine("Done navigating"))
browser.Navigate(uri)
do run();
[<EntryPoint;STAThread>]
let main argv =
BrowserApplication().Run()

Related

Is there a Geopoint class available for Winforms (or an Analogue thereof)?

In my UWP app, I use a Geopoint class:
using Windows.Devices.Geolocation;
. . .
List<Geopoint> locations;
In a Winforms app, this is not available - Geopoint is not recognized. Is there an analogous class available for Winforms apps?
The same is true for the BasicGeoposition object - not recognized.
UPDATE
I want the GeoPoint and BasicGeoposition classes so I can do things like this:
BasicGeoposition location = new BasicGeoposition();
location.Latitude = 36.59894360222391; // Monterey == 36.6002° N
location.Longitude = -121.8616426604813; // Monterey == 121.8947° W (West is negative)
Geopoint geop = new Geopoint(location);
await map.TrySetSceneAsync(MapScene.CreateFromLocation(geop));
cmbxZoomLevels.SelectedIndex = Convert.ToInt32(map.ZoomLevel - 1);
map.Style = MapStyle.Aerial3DWithRoads;
UPDATE 2
I tried the code provided in the answer:
this.UserControl1.myMap.AnimationLevel = AnimationLevel.Full;
this.userControl11.myMap.Loaded += MyMap_Loaded;
...but it won't compile. I don't have a UserControl11 (which is what the answer's code has), but I do have a UserControl1, yet it is not recognized:
This is the XAML in question (Bing Maps key obfuscated):
<UserControl x:Class="MyMaps.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:m="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF">
<Grid>
<m:Map CredentialsProvider="Gr8GooglyMoogly" x:Name="myMap" />
</Grid>
</UserControl>
To set the view of the Bing Maps WPF control, you can use SetView method. The method have different overloads, for example you can pass a Location(which you create based on the latitude and longitude of your desired location) and a zoom-level to the method like this:
var location = new Location(47.604, -122.329);
this.userControl11.myMap.SetView(location, 12);
Same can be achieved by setting Center and ZoomLevel.
Download or Clone the example
You can download or close the working example from here:
Clone r-aghaei/WinFormsWpfBingMaps
Download master.zip
Step by Step Example - Zoom into Seattle as initial view
Follow instructions in this post to create a Windows Forms project which uses WPF Bing Maps Control.
Handle the Load event of the Form and use the following code:
private void Form1_Load(object sender, EventArgs e)
{
this.userControl11.myMap.AnimationLevel = AnimationLevel.Full;
this.userControl11.myMap.Loaded += MyMap_Loaded;
}
private void MyMap_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
var location = new Location(47.604, -122.329);
this.userControl11.myMap.SetView(location, 12);
}
Make sure you use using Microsoft.Maps.MapControl.WPF;.
As a result, the map zooms in Seattle as center location:
More information:
You may want to take a look at the following links for more information:
How can I add a Bing Maps Component to my C# Winforms app?
Bing Maps WPF Control
Developing with the Bing Maps WPF Control
Bing Maps WPF Control API Reference
For those who are looking to use Windows Community Toolkit Map Control which is different from Bing Maps WPF Control, you can follow these steps to use Windows Community Toolkit Map Control for Windows Forms.
Note: Windows 10 (introduced v10.0.17709.0) is a prerequisite.
Create a Windows Forms Application (.NET Framework >=4.6.2 - I tried myself with 4.7.2)
Install Microsoft.Toolkit.Forms.UI.Controls NuGet package.
Add an app.manifest file: Right-click on project → Add New Item → Choose Application Manifest File (Windows Only) which is located under General node.
Open the app.manifest file and uncomment the supportedOS under <!-- Windows 10 -->:
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
Handle the Load event of your form and add the following code:
private void Form1_Load(object sender, EventArgs e)
{
var map = new MapControl();
map.Dock = DockStyle.Fill;
map.MapServiceToken = "YOUR KEY";
map.LoadingStatusChanged += async (obj, args) =>
{
if (map.LoadingStatus == MapLoadingStatus.Loaded)
{
var cityPosition = new BasicGeoposition() {
Latitude = 47.604, Longitude = -122.329 };
var cityCenter = new Geopoint(cityPosition);
await map.TrySetViewAsync(cityCenter, 12);
}
};
this.Controls.Add(map);
}
Also make sure you include required usings:
using Microsoft.Toolkit.Forms.UI.Controls;
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
Note 1: I was unable to add the control in designer because of an exception on design-time when I tried to drop the control on form, so I decided to use add it at run-time.
Note 2: You need to Get a Key to use map; however for test purpose you may ignore getting the key.
Run your application and see the result:
More information
MapControl for Windows Forms and WPF
Source code: Microsoft.Toolkit.Forms.UI.Controls.MapControl
WinForms control is a wrapper around WPF Windows.UI.Xaml.Controls.Maps.MapControl
Display maps with 2D, 3D, and Streetside views

Thread.CurrentThread.CurrentUICulture changes .net 4.8 when async is used

I started to use async operations in my WPF application and realized that Buttons, Labels are on the correct culture (hu-HU), but MessageBox.Show works on the culture of the operating system (en-US). I have resource files for both languages.
App.OnStartup contains
Thread.CurrentThread.CurrentCulture = new CultureInfo("hu-HU");
Thread.CurrentThread.CurrentUICulture = new CultureInfo("hu-HU");
When I press a button in my application and go to the VM I see that Thread.CurrentThread.CurrentUICulture is changed to "en-US" (Thread.CurrentThread.CurrentCulture remained "hu-HU"). I understand this was an issue before .net 4.6 (https://stackoverflow.com/a/30664385/5852947) but it should not be in 4.8. As I understand https://learn.microsoft.com/en-us/dotnet/api/system.globalization.cultureinfo?view=netcore-3.1#Async states the both culture should be inherited from the originating thread.
I also tried this without success.
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("hu-HU");
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("hu-HU");
UPDATE 1
I have a WPF application and I set the culture in OnStartup(). The culture is not hard coded I just wanted to simplify the code.
On the image you can see that the gui is in Hungarian but when I press a button and the corresponding ICommand run in the VM then CurrentUICulture is not correct. In debug mode at the execution of ICommand I also see that CurrentUICulture is not correct. I only set the culture in App.OnStartup. I had no problem until I did not use async. I changed OnStartup() to async because at one point it contains await.
protected async override void OnStartup(StartupEventArgs startupEventArgs)
{
base.OnStartup(startupEventArgs);
var cultureString = ConfigurationManager.AppSettings["Language"];
Thread.CurrentThread.CurrentCulture = new CultureInfo(cultureString);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(cultureString);
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo(cultureString);
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo(cultureString);
var mainWindowView = new MainWindowView();
// ...
mainWindowView.Show();
ScrollDownInTableTab(mainWindowViewModel);
}
Calling Wait() on a task blocks and potentially also deadlocks. Don't do this. A task represents an asynchronous operation that should be awaited.
As for the issue with culture, you can switch back to the old behaviour by setting the NoAsyncCurrentCulture switch to true in your App.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
<runtime>
<AppContextSwitchOverrides value="Switch.System.Globalization.NoAsyncCurrentCulture=true"/>
</runtime>
</configuration>

How to load WPF treeview resources from another assembly using MEF?

I'm creating a WPF application which loads its plugin using MEF.
How can I include resources from another assembly I'm loading using MEF?
Specifically I want to create an HierarchicalDataTemplate in external assembly and load it to a Treeview.Resources dynamically when composing the application on start.
Is something like this possible?
I'm using Caliburn.Micro if it matters but I'm sure the question applies to general WPF applications.
If you try to load static resources you should load the resource before loading the main window.
If you try to load dynamic resources you should load the resource before loading the view that uses the resource.
Any way you should add a reference to the resource by adding it to the Wpf Application merge dictionary while bootstrapping.
//On the bootstrapper add the following code
ResourceDictionary rd = new ResourceDictionary
{
Source =
new Uri(
"pack://application:,,,/DllName;component/Themes/ResourceName.xaml",
UriKind.RelativeOrAbsolute)
};
Application.Current.Resources.MergedDictionaries.Add(rd);
This is how I did it at the end.
Because Caliburn.Micro doesn't work properly if you use MEF's DirectoryCatalog to load your assemblies I had to do it manually. Bellow is the simplified part of the code that does it and loads the ResourceDictionary contained in the separate resources.xaml file.
FileInfo[] filesInfo = new DirectoryInfo(pluginPath).GetFiles("*.dll");
AssemblySource.Instance.AddRange(filesInfo.Select(fileInfo => Assembly.LoadFrom(fileInfo.FullName)));
// load resources from plugins
var dictionaries = App.Current.Resources.MergedDictionaries;
dictionaries.Clear();
foreach (FileInfo fileInfo in filesInfo)
{
string assemblyName = Path.GetFileNameWithoutExtension(fileInfo.Name);
string uriString = assemblyName + #";component/resources.xaml";
try
{
dictionaries.Add(new ResourceDictionary { Source = new Uri(uriString, UriKind.Relative) });
}
catch
{
// do some logging
}

WPF window will not open after adding namespace

So I have simple F# WPF application. It was working just fine without declaring a namespace and using multiple modules.
Now, it still compiles, but simply does nothing. Nothing in debug to show either.
This is the current code that does not work.
namespace Flow
module MainApp =
open System
open System.Windows
let app1 = new Application()
[<STAThread>]
app1.Run(new Main.MainWindow()) |>ignore
before it worked when it was like this
module MainApp
open System
open System.Windows
let app1 = new Application()
[<STAThread>]
app1.Run(new Main.MainWindow()) |>ignore
I can show the definition of MainWindow, but its very long, its a class that inherits from Window.
Let me know if that would help. Or if there is anything other information that I could give that would help with this issue.
Your original code relies on an implicit entry point:
"When a program has no EntryPoint attribute that explicitly indicates the entry point, the top level bindings in the last file to be compiled are used as the entry point."
You can either define a function in your module and explicitly mark that as the entry point:
namespace Flow
module MainApp =
open System
open System.Windows
let app1 = new Application()
[<EntryPoint>]
[<STAThread>]
let main args =
app1.Run(new Window())
Or you can continue to use an implicit entry point by including the namespace in the module name:
module Flow.MainApp
open System
open System.Windows
let app1 = new Application()
[<STAThread>]
app1.Run(new Window()) |>ignore

Visual F# Windows form closing after showing

Good Day,
I have just started learning visual F#, and it looks surprisingly fun to do. For my first project I got my hands dirty by immediately make a windows form to download info from a page and display it in a RichTextBox on the form. Problem is, once the form shows and information is downloaded, it immediately closes. How do I keep my masterpiece open for viewing? Any advice?
I have 2 Files currently:
Program.fs
Script1.fs
Program.fs is supposed to "create" the form, where Script1.fs is merely the entrypoint for the application.
Program.fs
namespace Program1
open System.Windows.Forms
module public HelloWorld =
let form = new Form(Visible = true, TopMost = true, Text = "Welcome to F#")
let textB = new RichTextBox(Dock = DockStyle.Fill, Text = "Initial Text")
form.Controls.Add textB
open System.IO
open System.Net
/// Get the contents of the URL via a web request
let http (url: string) =
let req = System.Net.WebRequest.Create(url)
let resp = req.GetResponse()
let stream = resp.GetResponseStream()
let reader = new StreamReader(stream)
let html = reader.ReadToEnd()
resp.Close()
html
textB.Text <- http "http://www.google.com"
Script1.fs
open Program1
[<EntryPoint>]
let main argv=
printfn "Running F# App"
HelloWorld.form.Show();
0
I need to reiterate, I started with F#. This is my first application I wrote. How do I keep the form open?
You need to call Application.Run and pass your form object into it. http://msdn.microsoft.com/en-us/library/system.windows.forms.application.run(v=vs.110).aspx
This will create a message loop, and keep your application alive until the form is closed.
open Program1
[<EntryPoint>]
let main argv=
printfn "Running F# App"
System.Windows.Forms.Application.Run(HelloWorld.form)
0

Resources