Navigation between Blazor Winforms pages - winforms

After a successfull experience with Blazor web server and Maui hybrid with Blazor, I'm now taking my first steps integrating my Blazor components in Winforms.
But I found no tutorials with samples of how to navigate between different pages/windows/forms.
Html links do not seem to work, and neither do NavigationManager:
Default page, app successfully loads it:
#inject NavigationManager NavigationManager
<div>
<button #onclick="OnClick">Go to page 2</button>
<a href="#" #onclick="#OnClick" #onclick:preventDefault>go to page 2</a>
<a href="page2" >go to page 2</a>
</div>
#code {
private void OnClick()
{
NavigationManager.NavigateTo("Page2");
}
}
Second page, no way to open it from previous one:
#page "/Page2"
<div>Wellcome to Page2</div>
Should I better navigate between Forms?
Update 1
I finally got it working having the component trigger an event on a form state class which instantiates a new form and shows it.
I guess I should better declare a singleton service with global appstate class in program.cs and hold all form instantiation from there, but I can't find the right way to declare it on a Blazor Winforms project.
My last version as follows, but appState is null when initializing Form1:
The singleton that communicates between Blazor compolnents and Winforms forms:
public class AppState
{
public event EventHandler<EventArgs>? GotoForm;
public void NavigateNext()
{
GotoForm?.Invoke(this,new EventArgs());
}
}
The component which feeds content to each form:
#inject AppState appState
<h3>Welcome to Form #FormNumber</h3>
<button #onclick="appState.NavigateNext">Goto page #AltFormNumber()</button>
#code {
[Parameter] public int FormNumber { get; set; }
int? AltFormNumber()=>FormNumber == null ? null : 3-FormNumber;
}
The default form:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebView.WindowsForms;
using Microsoft.Extensions.DependencyInjection;
public partial class Form1 : Form
{
[Inject] AppState appState { get; }
public Form1()
{
InitializeComponent();
var services = new ServiceCollection();
services.AddWindowsFormsBlazorWebView();
//services.AddSingleton<FormState>();
blazorWebView1.HostPage = "wwwroot\\index.html";
blazorWebView1.Services = services.BuildServiceProvider();
//var appState = blazorWebView1.Services.GetRequiredService<AppState>();
appState.GotoForm += NavigateNext;
blazorWebView1.RootComponents.Add<Pages.FormComponent>("#app",
new Dictionary<string, object?> { { "FormNumber", 1 } });
}
private void NavigateNext(object? sender, EventArgs args)
{
var frm = new Form2();
frm.Show();
}
}
The alternative form:
public partial class Form2 : Form
{
[Inject] AppState appState { get; }
public Form2()
{
InitializeComponent();
var services = new ServiceCollection();
services.AddWindowsFormsBlazorWebView();
//services.AddSingleton<FormState>();
blazorWebView1.HostPage = "wwwroot\\index.html";
blazorWebView1.Services = services.BuildServiceProvider();
//var appState = blazorWebView1.Services.GetRequiredService<AppState>();
appState.GotoForm += NavigateNext;
blazorWebView1.RootComponents.Add<Pages.FormComponent>("#app",
new Dictionary<string, object?> { { "FormNumber", 2 } });
}
private void NavigateNext(object? sender, EventArgs args)
{
var frm = new Form1();
frm.Show();
}
}
The Program.cs:
internal static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
var host = CreateHostBuilder().Build();
ServiceProvider = host.Services;
Application.Run(new Form1());
}
public static IServiceProvider ServiceProvider { get; private set; }
static IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder()
.ConfigureServices((context, services) => {
services.AddSingleton<AppState>();
});
}
}

Related

Getting issue with the dependency injection asp core 6 with winforms creation?

Github link to sample project
static void Main()
{
ApplicationConfiguration.Initialize();
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddTransient<Form1>();
services.AddTransient<Form2>();
});
var host = builder.Build();
using (var serviceScope = host.Services.CreateScope())
{
IServiceProvider services = serviceScope.ServiceProvider;
Application.Run(services.GetRequiredService<Form1>());
}
}
Form1 is MDI MdiParent where i am injecting Form 2
public partial class Form1 : Form
{
private readonly Form2 form2;
public Form1(Form2 form2)
{
InitializeComponent();
this.form2 = form2;
}
private void form2ToolStripMenuItem_Click(object sender, EventArgs e)
{
this.form2.MdiParent = this;
this.form2.Show();
}
}
When I Open Form2 by clicking from Menu it opens and close it by using [X] button
When i reopen it i am getting error
The form is disposed when closed.
I would suggest using a factory
static void Main() {
ApplicationConfiguration.Initialize();
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) => {
services.AddTransient<Form1>();
services.AddTransient<Form2>();
//Form2 factory delegate
services.AddSingleton<Func<Form2>>(sp => () => sp.GetRequiredService<Form2>());
});
var host = builder.Build();
using (var serviceScope = host.Services.CreateScope()) {
IServiceProvider services = serviceScope.ServiceProvider;
Application.Run(services.GetRequiredService<Form1>());
}
}
to initialize a new form every time the button is clicked.
public partial class Form1 : Form {
private readonly Func<Form2> factory;
public Form1(Func<Form2> factory) {
InitializeComponent();
this.factory = factory;
}
private void form2ToolStripMenuItem_Click(object sender, EventArgs e) {
Form2 form2 = factory();
form2.MdiParent = this;
form2.Show();
}
}

Change the View on button Click in WPF MVVM Pattern

I have 3 buttons on one usercontrol (usercontrol1.xaml) in the Window . Now on-click of button 1 ,I want to switch the view to another usercontrol (usercontrol2.xaml), which again have 3 buttons and so on.
How to implement in MVVM Pattern in WPF?
Be aware that im using caliburn micro for this example
private IEventAggregator _eventAggregator => IoC.Get<IEventAggregator>(key: nameof(EventAggregator));
private IWindowManager _windowManager => IoC.Get<IWindowManager>(key: nameof(WindowManager));
public ShellViewModel(IEventAggregator eventAggregator)
{
_eventAggregator.Subscribe(this);
}
public string _firstName;
// public ShellViewModel page = new ShellViewModel();
public string FirstName
{
get {
return _firstName;
}
set
{
_firstName = value;
NotifyOfPropertyChange(() => FirstName);
}
}
public ICommand ConvertTextCommand
{
get { return new DelegateCommand(ConvertText); }
}
void ConvertText()
{
//string url = "https://www.google.com/";
string url = FirstName;
string result;
using (HttpClient client = new HttpClient())
{
using (HttpResponseMessage response = client.GetAsync(url).Result)
{
using (HttpContent content = response.Content)
{
result = content.ReadAsStringAsync().Result;
}
}
}
//(MainWindow)Application.Current.MainWindow).txtForm1TextBox.Text = "Some text";
//Application.Current.Resources.Add("PageSource", result);
// NavigationService.NavigateToViewModel<SecondViewModel>("Hello");
_windowManager.ShowWindow(new PageSourceViewModel(_eventAggregator), null);
_eventAggregator.PublishOnUIThread(result);
}
You can check caliburn micro and see that you can just create a new view model in a window manager instance
here is also 2 links to 2 tutorials that helped me solve this issue for MVVM
https://www.youtube.com/watch?v=laPFq3Fhs8k
https://www.youtube.com/watch?v=9kGcE9thwNw&list=LLy8ROdSzpPJnikdZQ1XPZkQ&index=30&t=0s
the first tutorial will help you to get a general idea. The second will help you with events and you can look back to my code and see how i handled a new window instance.
You can also call the same view model for a new instance of the same window like you said in the question
You will also need to make a boostrapper class. For my example i did it like this.
public class Bootstrapper : BootstrapperBase
{
private readonly SimpleContainer _container =
new SimpleContainer();
public Bootstrapper()
{
Initialize();
}
protected override void Configure()
{
_container.Instance<IWindowManager>(new WindowManager());
_container.Singleton<IEventAggregator, EventAggregator>();
_container.PerRequest<ShellViewModel>();
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
_container.Instance<SimpleContainer>(_container);
_container.Singleton<IWindowManager, WindowManager>(key: nameof(WindowManager))
.Singleton<IEventAggregator, EventAggregator>(key: nameof(EventAggregator));
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);
}
}

Navigation between lazy loaded modules with Prism and WPF

Hello I'm trying to setup an architecture where only one module gets booted when the app is launched. Then I'd like to lazy load other modules based on the user's actions.
To achieve this in my app.xaml.cs I have one module loaded at bootstrap time (MainModule), and an other has InitializationMode = InitializationMode.OnDemand
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
Type BlipModuleType = typeof(BlipModule);
moduleCatalog.AddModule(new ModuleInfo()
{
ModuleName = BlipModuleType.Name,
ModuleType = BlipModuleType.AssemblyQualifiedName,
InitializationMode = InitializationMode.OnDemand
});
moduleCatalog.AddModule<MainModule>();
}
then my main module, which displays the view correctly, has a single view registered to the only region available:
public class MainModule : IModule
{
private readonly IRegionManager _regionManager;
public MainModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void OnInitialized(IContainerProvider containerProvider)
{
_regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(ViewA));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
The lazy loaded module has the same structure, registering a different view (which works properly if i decide to use it as my main module)
public class BlipModule : IModule
{
private readonly IRegionManager _regionManager;
public BlipModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void OnInitialized(IContainerProvider containerProvider)
{
_regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(ViewB));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
finally I have a Command in the viewmodel of my MainModule ViewA, that is supposed to load the new module and navigate to it.
public class ViewAViewModel : BindableBase
{
const string BlipModuleName = "BlipModule";
public ReactiveCommand ChangeRoute { get; set; } = new ReactiveCommand();
public ViewAViewModel(IRegionManager regionManager, IModuleManager moduleManager)
{
ChangeRoute.Subscribe(res =>
{
moduleManager.LoadModule(BlipModuleName);
});
moduleManager.LoadModuleCompleted += (s, e) =>
{
if (e.ModuleInfo.ModuleName == BlipModuleName)
{
regionManager.RequestNavigate(RegionNames.ContentRegion, new Uri(BlipModuleName, UriKind.Relative));
}
};
}
}
The viewB of the BlipModule is actually loaded (I get a hit if I set a breakpoint in the view's constructor), but instead of the view I get a white page with "System.Object" inside of it.
Any idea? thanks!
You want to RegisterForNavigation instead of RegisterViewWithRegion.

Problem loading view with MEF and ExportAttribute

I have a WPF app and I'm trying to use MEF to load viewmodels and view.
I can't successfully load Views.
The code:
public interface IContent
{
void OnNavigatedFrom( );
void OnNavigatedTo( );
}
public interface IContentMetadata
{
string ViewUri { get; }
}
[MetadataAttribute]
public class ExtensionMetadataAttribute : ExportAttribute
{
public string ViewUri { get; private set; }
public ExtensionMetadataAttribute(string uri) : base(typeof(IContentMetadata))
{
this.ViewUri = uri;
}
}
class ViewContentLoader
{
[ImportMany]
public IEnumerable<ExportFactory<IContent, IContentMetadata>> ViewExports
{
get;
set;
}
public object GetView(string uri)
{
// Get the factory for the View.
var viewMapping = ViewExports.FirstOrDefault(o =>
o.Metadata.ViewUri == uri);
if (viewMapping == null)
throw new InvalidOperationException(
String.Format("Unable to navigate to: {0}. " +
"Could not locate the View.",
uri));
var viewFactory = viewMapping.CreateExport();
var view = viewFactory.Value;
return viewFactory;
}
}
I supposed to use this code like this:
1)Decorate a User control
[Export(typeof(IContent))]
[ExtensionMetadata("CustomPause")]
[PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.NonShared)]
public partial class CustomPause : Page , IContent, IPartImportsSatisfiedNotification
{
public CustomPause()
{
InitializeComponent();
}
}
2) Compose the parts:
var cv = new CompositionContainer(aggregateCatalog);
var mef = new ViewContentLoader();
cv.ComposeParts(mef);
3) Load the view at runtime given a URI, for example:
private void CustomPause_Click(object sender, RoutedEventArgs e)
{
var vc = GlobalContainer.Instance.GetMefContainer() as ViewContentLoader;
MainWindow.MainFrame.Content = vc.GetView ("CustomPause");
}
Problem is this line in the GetView method fails:
var viewMapping = ViewExports.FirstOrDefault(o =>
o.Metadata.ViewUri == uri);
The query fails and so viewMapping is null but composition seems ok and I can see that ViewExports contains an object of type:
{System.ComponentModel.Composition.ExportFactory<EyesGuard.MEF.IContent, EyesGuard.MEF.IContentMetadata>[0]
I don't know where I'm wrong. Do you have a clue?
Gianpaolo
I had forgot this
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
in the MetadataAttribute

net.pipe service host in WPF app

The contract:
[ServiceContract]
public interface IDaemonService {
[OperationContract]
void SendNotification(DaemonNotification notification);
}
The service:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class DaemonService : IDaemonService {
public DaemonService() {
}
public void SendNotification(DaemonNotification notification) {
App.NotificationWindow.Notify(notification);
}
}
In WPF app I do the following:
using (host = new ServiceHost(typeof (DaemonService), new[] {new Uri("net.pipe://localhost")})) {
host.AddServiceEndpoint(typeof (IDaemonService), new NetNamedPipeBinding(), "AkmDaemon");
host.Open();
}
This WPF app launches another app like this:
Task.Factory.StartNew(() => {
var tpm = new Process { StartInfo = { FileName = "TPM" } };
tpm.Start();
}
});
The app named TPM starts properly. Then I do attach to process in the debugging menu of Visual Studio and I see the client says that nobody is listening at the endpoint.
Here is the client:
[Export(typeof(DaemonClient))]
public class DaemonClient : IHandle<DaemonNotification> {
private readonly ChannelFactory<IDaemonService> channelFactory;
private readonly IDaemonService daemonServiceChannel;
public DaemonClient(IEventAggregator eventAggregator) {
EventAggregator = eventAggregator;
EventAggregator.Subscribe(this);
channelFactory = new ChannelFactory<IDaemonService>(new NetNamedPipeBinding(),
new EndpointAddress("net.pipe://localhost/AkmDaemon"));
daemonServiceChannel = channelFactory.CreateChannel();
}
public IEventAggregator EventAggregator { get; private set; }
public void Handle(DaemonNotification message) {
daemonServiceChannel.SendNotification(message); //Here I see that the endpoint //is not found
}
public void Close() {
channelFactory.Close();
}
}
EndpointNotFoundException There was no endpoint listening at "net.pipe://localhost/AkmDaemon"... blablabla
You are creating your ServiceHost in a using statement, so it is disposed right after the Open call. The Dispose call closes the ServiceHost.
using (host = new ServiceHost(...))
{
host.AddServiceEndpoint(...);
host.Open();
}
// ServiceHost.Dispose() called here
Just drop the using block.

Resources