Installation of MudBlazor in a Blazor winforms application - winforms

I was going through the installation for MudBlazor and I am having some issues with the following lines of code in the installation guide...
using MudBlazor.Services;
builder.Services.AddMudServices();
winforms does not contain a builder by default so I altered the program.cs file as figured below...
internal static class Program
{
public static IServiceProvider? ServiceProvider { get; set; }
static void ConfigureServices()
{
var services = new ServiceCollection();
services.AddMudServices();
ServiceProvider = services.BuildServiceProvider();
}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
ConfigureServices();
Application.Run(new Form1());
}
}

Related

CaliburnMicro IEventAggregator won't work with ChildViews

and sorry if this is a somewhat easy question, but I'm still new to CaliburnMicro and EventAggregator is proving to be the worst thing to learn by a long mile. Anyway, let's go to it. I have an app with a main ShellView and a bunch of ChildViews that display various information, and I need certain parameters to be shared between the Views. It did not take very long to find I need to use EventAggregator, but with Net6 I could not make it work, no chance. I found this app someone else did in Net3.1 and CaliburnMicro where a new window is created, on this second window there is a TextBox and a send button. Whatever you type here gets sended to the Main window. I studied the code and replicated the app succesfully, also with the latest version of Caliburn and Net6.
BUT then I decided to modify the app and instead of having a new window, now I have exactly the same but with a ChildView inside the ShellView, and here is where nothing works.
1st on the Bootstrapper
public class Bootstrapper : BootstrapperBase
{
private SimpleContainer _container;
public Bootstrapper()
{
Initialize();
}
protected override void Configure()
{
_container = new SimpleContainer();
_container.Singleton<IWindowManager, WindowManager>();
_container.Singleton<IEventAggregator, EventAggregator>();
_container.PerRequest<ShellViewModel>();
}
protected override object GetInstance(Type service, string key)
{
var instance = _container.GetInstance(service, key);
if (instance != null)
return instance;
throw new InvalidOperationException("Could not locate any instances.");
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<ShellViewModel>();
}
As seen, I've created a SimpleContainer, then initialised it as a Singleton.
Also _container.Singleton<IWindowManager, WindowManager>(); is no longer needed as I am not opening a new window anymore, so that could be commented but as I had so many issues, I let it be just in case for this last try before posting this question.
2nd I've created a class for the message IHandle to manage.
public class EventMessage
{
public string Text { get; set; }
}
3rd I created and edited the ChildViewModel (still called SecondWindowViewModel as this experiment is directly derivated from the original worknig one) and ChildView (well SecondWindowView still). Note that SecondWindowView is a WPF User Control, not a Window.
class SecondWindowViewModel : Screen
{
private readonly IEventAggregator _eventAggregator;
private string _secondTextBox;
public string SecondTextBox
{
get { return _secondTextBox; }
set { _secondTextBox = value; NotifyOfPropertyChange(() => SecondTextBox); }
}
public SecondWindowViewModel(IEventAggregator eventAggregator)
{
this._eventAggregator= eventAggregator;
}
public void SendBack()
{
EventMessage ToSend = new EventMessage();
ToSend.Text = SecondTextBox;
_eventAggregator.PublishOnUIThreadAsync(ToSend);
}
As seen, I have an IEventAggregator _eventAggregator, then on the constructor of the class I added this._eventAggregator= eventAggregator; and then on the method SendBack that is called upon pressing the button I send the message with SubscribeOnUIThread.
And lastly the ShellViewModel:
public class ShellViewModel : Conductor<Object>, IHandle<EventMessage>
{
private readonly IEventAggregator _eventAggregator;
private string _parentText;
public string ParentText
{
get { return _parentText; }
set { _parentText = value; NotifyOfPropertyChange(() => ParentText); }
}
public ShellViewModel(IEventAggregator eventAggregator)
{
ActivateItemAsync(new SecondWindowViewModel(_eventAggregator));
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
}
public Task HandleAsync(EventMessage message, CancellationToken cancellationToken)
{
ParentText = message.Text;
return Task.CompletedTask;
}
//public void NewWindow()
//{
//WindowManager wm = new WindowManager();
//SecondWindowViewModel swm = new SecondWindowViewModel(_eventAggregator);
//wm.ShowWindowAsync(swm);
//}
}
}
Now here instead of inheriting from Screen and IScreen, I inherit from Conductor because I want to have that ChildView on my form. NewWindow is how it worked before but now that button no longer works as I don't need to launch a new window anymore, that's why it is commented out.
As seen, on the contructor I subscribe _eventAggregator, and then HandleAsync does the job of receiving the message and assign it to a variable. Now on the Caliburn Documentatin the method to use is public void Handle() but that no longer works, that's the only way I managed to make it work.
Now when I run this the app does load and seems to work just fine, but as soon as the SendBack() method gets called (in SecondWindowViewModel) the line _eventAggregator.PublishOnUIThreadAsync(ToSend); launches an exception System.NullReferenceException: 'Object reference not set to an instance of an object.'
From my understanding EventAggregator should not care if I'm sending the message to a Window or a user panel or anything. Only thnig I changed is commenting out NewWindow and deleting the old SecondWindowWiew WPF Window and replacing it with a new SecondWindowView WPF User Control with the exact same XAML, then in ShellWiev added bellow a <ContentControl x:Name="ActiveItem"/>.
I'm a bit of a loss here, I've been trying everything, coping the code from the documentation, looking for tutorials online, other StackOverflow questions, and while I could make the UI load and make HandleAsync work, for 3 days straight I could not make it work with ChildViews. That code I wrote does work with a new window. I even run into problems of ShellView straight up not loading unless putting a new constructor that takes no parameters and empty inside, but that's for another day.
Sorry for the extra long post, but I think it's important to put all the information out there. Thank you for your time and again, sorry if this is somewhat of a dumb question, but we all have to start somewhere no?

Using Prism for building a Plugin (.dll)

I am developing a plugin to be used for a software I use. The software plugin is attached to the software by attaching the .dll produced from my code. Therefore, the software's documentation requires you to have a certain class (call it CPlugin) as the plugins entry point.
All the tutorials I am reading with Prism is where you initiate your project as a WPF Application. This way your project will have App.xaml and App.cs files where you start to implement your Prism framework. Compiling the code also (for a WPF application) will produce a .exe not a .dll.
The way I have my plugin setup is I start my project with a C# class. I would then create my CPlugin class and initiate all my variables and then display my MainView which creates my ViewModel and take it from there. There is no App.xaml or App.cs. I am not sure how to use Prism with the constraints I have.
This is the software I am developing the plugin for: https://www.csiamerica.com/products/etabs
Upon installation in the install directory; the API helpfile can be found which explains how to develop or initiate a plugin. Here is a sample of the relevant information:
In your plugin, all functionality must be implemented in a class called cPlugin.
Class cPlugin must contain a subroutine cPlugin.Main that has two
reference arguments of types ETABSv1.cSapModel and
ETABSv1.cPluginCallback
Also
Adding a .NET Plugin
The process for adding a .NET plugin is much simpler. In the External Plugin Data form, the user should simply browse for and select the plugin .NET DLL and click the "Add" button
Here is some sample code for a plugin that displays an empty window:
Create a C# Class Library file (.NET Framework), reference the API as one of my references.
CPlugin.cs:
using Combonito.Views; // Access Views (open window)
using CSiAPIv1; //to Access ETABS/SAP2000 API
using System;
using static Globals;
namespace Combonito
{
// cPlugin has to be implemented in ETABS Plugins, it has to contain two functions Main() and Info()
public class cPlugin
{
private MainView _MyForm;
//Entry point of plugin - has to exist with this exact signature
// must call Finish() when plugin is closed!
public void Main(ref cSapModel _SapModel, ref cPluginCallback _ISapPlugin)
{
ParentPluginObject = this;
SapModel = _SapModel;
ISapPlugin = _ISapPlugin;
try
{
_MyForm = new MainView(); //Create MainView
_MyForm.ShowDialog(); // Display window
}
catch (Exception ex)
{
try
{
ISapPlugin.Finish(1);
Console.WriteLine(ex);
}
catch (Exception ex1)
{
Console.WriteLine(ex1);
throw;
}
}
}
// return value should be 0 if successful
public int Info(ref string txt)
{
try
{
txt = "Plugin is written by Moustafa El-sawy (mk.elsawy#live.com)";
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
return 0;
}
//Deconstructor to clean up
~cPlugin()
{
Console.WriteLine(GC.GetGeneration(0));
}
}
}
I then have an empty window MainView and A viewmodel MainWindowViewModel.
Edit: Here is the boiler initial to start any plugin similar to what I showed above but has more explanation https://kinson.io/post/etabs-plugin-quickstart/
Your starting point should be Bootstrapper
First you need to install:
Prism.Unity
Prism.Wpf
Need to create your bootstrapper based on
https://github.com/PrismLibrary/Prism/blob/master/src/Wpf/Prism.Wpf/PrismBootstrapperBase.cs
Override the virtual method to create Shell (which is your main view that contains regions
Override the virtual method to configure your container.
Finally register your views and viewmodels
P.S.: Consider using an interface for each registered type e.g. IShellViewModel
using Prism;
using Prism.Ioc;
using Prism.Unity;
using System.Windows;
namespace PrismTest
{
public class Bootstrapper : PrismBootstrapperBase
{
protected override IContainerExtension CreateContainerExtension()
{
return new UnityContainerExtension();
}
protected override DependencyObject CreateShell()
{
return Container.Resolve<Shell>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<IShellViewModel, ShellViewModel>();
}
}
}
Call your Bootstrapper from your plugin :
Bootstrapper bootstrapper = new Bootstrapper();
bootstrapper.Run();
Your View (Shell)
<Window
x:Class="PrismTest.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PrismTest"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Shell"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<TextBox Text="{Binding MyText}" />
</Grid>
</Window>
In Shell.xaml.cs: Use your view model. This can be also automatically injected
public partial class Shell : Window
{
private readonly IUnityContainer _container;
public Shell(IUnityContainer container)
{
InitializeComponent();
_container = container;
this.DataContext = _container.Resolve<IShellViewModel>();
}
}
Your ViewModel
public class ShellViewModel : BindableBase, IShellViewModel
{
private string m_MyText = "Shell ViewModel Text";
public string MyText
{
get { return m_MyText; }
set { SetProperty(ref m_MyText, value); }
}
}
Your interface:
internal interface IShellViewModel
{
string MyText { get; set; }
}
Resulting view;

WPF and EntityFrameworkCore - Adding migration gives "No database provider has been configured for this DbContext"

Note I have read the large number of SO answers that appear to be similar, but I am already doing what they suggested, so I don't know if there is some difference with WPF (they all seem to relate to ASP.NET). Also, most answers relate to run-time errors, not ones when adding migrations.
I'm trying to set up a .NET Core 3 WPF project that uses EntityFrameWork Core, but am having problems adding a migration. I have set up my context as follows...
public class ApplicationDbContext : DbContext {
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options) {
}
public ApplicationDbContext() {
}
public DbSet<Product> Products { get; set; }
}
The parameterless constructor is there, as without it I get an exception Unable to create an object of type 'ApplicationDbContext' when trying to add a migration.
My App.xaml.cs contains the following...
public partial class App {
public IServiceProvider ServiceProvider { get; private set; }
public IConfiguration Configuration { get; private set; }
protected override void OnStartup(StartupEventArgs e) {
IConfigurationBuilder builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appSettings.json", optional: false, reloadOnChange: true);
Configuration = builder.Build();
ServiceCollection serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
ServiceProvider = serviceCollection.BuildServiceProvider();
MainWindow mainWindow = ServiceProvider.GetRequiredService<MainWindow>();
mainWindow.Show();
}
private void ConfigureServices(IServiceCollection services) {
// Configuration
services.Configure<AppSettings>(Configuration.GetSection(nameof(AppSettings)));
// Database
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("SqlConnection")));
// Windows
services.AddTransient(typeof(MainWindow));
}
}
I realise that some of this is irrelevant, but thought I'd show the whole class in case it reveals something I missed. The code is based on this blog post.
However, when I try to add a migration, I get an exception "No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext."
As far as I can see, I have configured the database provider. I put a breakpoint in the ConfigureServices method, and can see that services.AddDbContext is called with the correct connection string.
Anyone any ideas what I've missed?
UPDATE I tried connecting to an existing database, and it worked absolutely fine, so it looks like the database provider has been configured correctly. It's only when I try to add a migration that I get the exception.
UPDATE 2 It seems that the migration tool is using the parameterless constructor on the context, which is why it thinks the provider hasn't been configured. If I remove the lines that configure it from App.xaml.cs, and instead override the OnConfiguringmethod to call UseSqlServer then the migration works fine. However, apart from the fact that I've not seen anyone else doing this (which makes me wonder if it's really the right way to do it), I don't see how to get the connection string from the config file. I can't inject an IConfiguration parameter, as the whole issue is that migrations requires a parameterless constructor.
It's actually quite simple with .Net Core 3.1 and EF Core Version 5, Entity Framework will look at the entry point class for the static function CreateHostBuilder, in my case that would be the App class in app.xaml.cs.
Not entirely sure the convention required prior to .Net Core 3.1. From my experience it had something to do with having a Startup class with .Net Core 2.1 and ASP.Net.
https://learn.microsoft.com/en-us/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli
My solution:
public partial class App : Application
{
/// <summary>
/// Necessary for EF core to be able to find and construct
/// the DB context.
/// </summary>
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
// Configure Application services
.ConfigureServices((context, services) =>
{
ConfigureServices(context, services);
});
}
/// <summary>
/// Not necessary but I prefer having a separate function for
/// configuring services.
/// </summary>
private static void ConfigureServices(HostBuilderContext context, IServiceCollection services)
{
...
}
/// <summary>
/// Hold the built Host for starting and stopping
/// </summary>
private readonly IHost AppHost;
/// <summary>
/// Constructor
/// </summary>
public App()
{
// Create Application host
AppHost = CreateHostBuilder(new string[] { }).Build();
}
/// <summary>
/// App Startup Event Handler
/// </summary>
private async void Application_Startup(object sender, StartupEventArgs e)
{
// Start the application host
await AppHost.StartAsync();
...
}
/// <summary>
/// App Exit Event Handler
/// </summary>
private async void Application_Exit(object sender, ExitEventArgs e)
{
// Kill the application host gracefully
await AppHost.StopAsync(TimeSpan.FromSeconds(5));
// Dispose of the host at the end of execution
AppHost.Dispose();
}
}
You need to implement IDesignTimeDbContextFactory. There is a lot of hidden plumbing in an ASP.NET Core app that deals with wiring up the apps service provider so it can be found by the dotnet ef tooling, but no such plumbing exists in WPF. In addition the ef tools know nothing about WPF events so your OnStartup method isn't going to even be called (an instance the class wont even get created) to create your DI setup so that the ef tools can find your DBContext.
Move the code that creates the ServiceProvider into the constructor, other than the bit that looks up the main window and displays it.
Implement IDesignTimeDbContextFactory<ApplicationDbContext>. In the implemented CreateDbContext method return ServiceProvider.GetRequiredService<ApplicationDbContext>()
The tooling will then create an instance of your class, which will setup DI, and call that method to get your (now configured) DB Context and it should work.
I'd also recommend moving to HostBuilder based config (that blog post was written before the final version of Core 3 was released). You will find an updated version of the same post here

NO definition for 'SetBeforeAccess' TokenCache

I've directly copied a GitHub sample code and cannot get rid of these errors.
Error CS1061 'TokenCache' does not contain a definition for 'SetBeforeAccess'
I've included Microsoft.Identity.Client in my using statements.
using System.IO;
using System.Security.Cryptography;
using Microsoft.Identity.Client;
namespace TokenCache1
{
static class TokenCacheHelper
{
public static TokenCache GetUserCache()
{
if (usertokenCache == null)
{
usertokenCache = new TokenCache();
usertokenCache.SetBeforeAccess(BeforeAccessNotification);
usertokenCache.SetAfterAccess(AfterAccessNotification);
}
return usertokenCache;
}
static TokenCache usertokenCache;
/// <summary>
/// Path to the token cache
/// </summary>
public static string CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + "msalcache.txt";
private static readonly object FileLock = new object();
public static void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
lock (FileLock)
{
args.TokenCache.Deserialize(File.Exists(CacheFilePath)
? File.ReadAllBytes(CacheFilePath)
: null);
}
}
I'm using .NET4.5 and UWP apps.
This could have occurred due to missing MSAL library. It can be installed from NuGet on Visual Studio itself.
On Visual Studio, select the Tools menu, choose NuGet Package Manager, then Package Manager Console, and type the following command:
Install-Package Microsoft.Identity.Client -Pre
I had the same problem:
TokenCacheExtensions class was not accessible when the reference added from packages\Microsoft.Identity.Client.1.1.3-preview0012\Microsoft.Identity.Client.dll
But when I added the reference from
packages\Microsoft.Identity.Client.1.1.3-preview0012\lib\net45\Microsoft.Identity.Client.dll
then i was able to access the TokenCacheExtension class.

Xamarin Forms Maps - Customization of Tiles

I'm coming today to speak about the Tiles of the map !
For a personnal project and the project of my company, I need to customize a lot my Xamarin.Forms.Maps. I found this tutorial Custom Map Tiles in Xamarin.Forms which speak only about Android and iOS (one more time..) However, I would love to know how does it works for WinPhone 8.1 and/or UWP.
Also, Because it uses Mapbox, I would like to know if this project is really make to be available for a long time? (I ask only for those who knows something about this project, because I don't know juste by reading).
As I know, some nuget package about it exists but without making what I really want (I want to custom the tiles over each plateform)
If you have a website about it or if you already did it, can you give me some directions or any help please? Thank !
EDIT 1
I found this code for the UWP renderer, but it doesn't change the map tiles..
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace MapTileProject.UWP.Renderer
{
public class CustomMapRenderer : MapRenderer
{
CustomMap customMap;
MapControl mapControl;
protected override void OnElementChanged(ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
customMap = e.NewElement as CustomMap;
mapControl = Control as MapControl;
UpdateTiles();
}
}
private void UpdateTiles()
{
Debug.WriteLine("BEGINING !");
HttpMapTileDataSource dataSource = new HttpMapTileDataSource(customMap.MapTileTemplate);
MapTileSource tileSource = new MapTileSource(dataSource);
mapControl.TileSources.Add(tileSource);
Debug.WriteLine("END !");
}
}
}
found this code for the UWP renderer, but it doesn't change the map tiles
If you check the web request using Fiddler, you will see the request API URL was incorrect:
Ref Overlay tiles from a tile source
The standard HttpMapTileDataSource in UWP should be like this:
http://www.web service name.com/z={zoomlevel}&x={x}&y={y}
It includes three replaceable parameters for the X and Y coordinates and the zoom level: {zoomlevel}, {x}, {y}
So we need to convert your MapTileTemplate string first:
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace MapTileProject.UWP.Renderers
{
public class CustomMapRenderer : MapRenderer
{
CustomMap customMap;
MapControl mapControl;
protected override void OnElementChanged(ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
customMap = e.NewElement as CustomMap;
mapControl = Control as MapControl;
UpdateTiles();
}
}
/// <summary>
/// Convert MapTileTemplate string to fit UWP HttpMapTileDataSource
/// </summary>
/// <param name="mapTileTemplate"></param>
/// <returns></returns>
private string GetTileTemplateForUWP(string mapTileTemplate)
{
return mapTileTemplate.Replace("{z}", "{zoomlevel}");
}
private void UpdateTiles()
{
Debug.WriteLine("BEGINING !");
HttpMapTileDataSource dataSource = new HttpMapTileDataSource(GetTileTemplateForUWP(customMap.MapTileTemplate));
MapTileSource tileSource = new MapTileSource(dataSource);
mapControl.TileSources.Add(tileSource);
Debug.WriteLine("END !");
}
}
}
Screenshot:

Resources