I'm getting started with WinUI3. Having no previous experience with UWP or WPF or even much C#, it's tough going.
Today's question is how to display a simple dialog at startup. Consider that we start with a simple app, as generated by Visual Studio. We have a MainWindow class defined in MainWindow.xaml.cs and its associated XAML (MainWindow.xaml). I believe the class is called a code-behind class.
So I want to do something as simple as display a dialog when the (desktop) app runs. It looks as though a ContentDialog is the way to go. But how to display it? As I understand it, I'm going to need to set the XamlRoot, so naively I tried this:
public MainWindow()
{
this.InitializeComponent();
DisplayDialog();
}
private async void DisplayDialog()
{
var dlg = new ContentDialog();
dlg.XamlRoot = this.Content.XamlRoot; // <-- set the XAML root here, but it's null
dlg.Content = "Hello World";
await dlg.ShowAsync();
}
This doesn't work. When called, the main window's XAML root is null and trying to show the dialog throws an exception:
How do I detect when it's ok to use the main window's XAML root? This issue seems to hint at an OnLoaded event, but I can't find anything about OnLoaded events in WinUI. Did I miss something?
This only way I can get this to work is to hook into the window's button and respond to its Loaded event, i.e.
public MainWindow()
{
this.InitializeComponent();
myButton.Loaded += MyButton_Loaded; // <-- hack
}
private void MyButton_Loaded(object sender, RoutedEventArgs e)
{
DisplayDialog();
}
private async void DisplayDialog()
{
var dlg = new ContentDialog();
dlg.XamlRoot = this.Content.XamlRoot; // <-- this is non-null now!
dlg.Content = "Hello World";
await dlg.ShowAsync();
}
But this feels really dirty. I don't even know if it's guaranteed that the XamlRoot will be non-null just because a button has loaded. And anyway, latching onto the button load seems very much like a hack. It relies on there being a button for one thing!
So how should I achieve the simple task of putting a dialog on the screen when all I have is the main window?
All help very gratefully received. Please try to make any answers as newbie-friendly as possible.
Unfortunately, the MainWindow has no Loaded events. What you can do is to work with a Page instead. So basically use the MainWindow as a "Window" and use Pages for your contents.
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.Loaded += MainPage_Loaded;
}
private async void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var dlg = new ContentDialog();
dlg.XamlRoot = this.XamlRoot;
dlg.Content = "Hello World";
await dlg.ShowAsync();
}
}
MainWindow.xaml
<Window
x:Class="ContentDialogs.MainWindow"
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="using:ContentDialogs"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<local:MainPage />
</Window>
A WinUI Window is just an abstraction of each of the low-level window implementations used by supported (UWP and desktop) app models.
The "trick" is to handle the Loaded event of the window's root element. The default template includes a Grid root element. A "generic" solution could be implemented something like this:
public MainWindow()
{
this.InitializeComponent();
var root = this.Content as FrameworkElement;
if (root != null)
root.Loaded += async (s, e) => await DisplayDialog();
}
private async Task DisplayDialog()
{
var dlg = new ContentDialog();
dlg.XamlRoot = this.Content.XamlRoot;
dlg.Content = "Hello World";
await dlg.ShowAsync();
}
Also tote that an async method, with the exception of event handlers, should return a Task or a Task<T> and be awaited by the caller.
Related
I would like to load some Data before I Render my Blazor Application because in depndency to the loaded data I would like to render my app (layout, navbar ...)
Now I want to use the OnInitialised method instead of OnInitialisedAsync and with no async and await keywords.
But now I had a problem to convert the data which I get back from my API.
protected override void OnInitialized()
{
try
{ Console.WriteLine("Test1Mainasync");
LoadCategories();
}
catch (Exception e)
{
jsRuntime.ToastrError(e.Message);
}
}
private void LoadCategories()
{
IEnumerable<CategorieDTO> CategoriesInit1 = new List<CategorieDTO>();
CategoriesInit1 = categorieService.GetAllCategories();
SD.Categories = CategoriesInit1.ToList();
//foreach(var categorie in CategoriesInit){
// SD.Categories.Append(categorie);
//}
Console.WriteLine("Test1Main");
}
Has someone an idea why this converting issues happen?
I think you have this method:
public async Task<IEnumerable<CategorieDTO>> GetAllCategories()
and you should call it this way:
private async Task LoadCategories()
{
IEnumerable<CategorieDTO> CategoriesInit1 = new List<CategorieDTO>();
CategoriesInit1 = await categorieService.GetAllCategories();
and:
protected override async Task OnInitializedAsync()
{
try
{ Console.WriteLine("Test1Mainasync");
await LoadCategories();
}
Has someone an idea why this converting issues happen?
In your code CatagiesInit1 is a Task, it's not a List<CategorieDTO>. You only get the List<CategorieDTO> when the task completes which you have no control over as you don't await the completion of the Task. In all likelyhood, your sync code will run to completion before that happens.
If your CategoryService returns a Task then the code that handles it must be async code. You can't escape from the async world back into the sync world without consequencies. If you want to live in the sync world then all the data pipeline code also needs to be blocking sync code.
If I understand your comments correctly, you want nothing to render until a certain set of conditions are met. If so add some standard Loading... component code to the page if it's page specific or App.razor if it's on initial load, or say MainLayout if it's application wide.
Here's a quick an dirty example:
<Router AppAssembly="#typeof(App).Assembly">
<Found Context="routeData">
#if (Loaded)
{
<RouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)" />
<FocusOnNavigate RouteData="#routeData" Selector="h1" />
}
else
{
<div class="m-2 p-5 bg-secondary text-white">
<h3>Loading.....</h3>
</div>
}
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="#typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
#code {
private bool Loaded;
protected override async Task OnInitializedAsync()
{
Loaded = false;
// simulate getting the data first
await Task.Delay(5000);
Loaded = true;
}
}
Your call to API endpoint return an awaitable task but not the IEnumerable, So you can not assign awaitable task to IEnumerable so this piece of code wont work
private void LoadCategories()
{
IEnumerable<CategorieDTO> CategoriesInit1 = new List<CategorieDTO>();
CategoriesInit1 = categorieService.GetAllCategories();
}
You should have your LoadCategories function like this
private async Task LoadCategories()
{
IEnumerable<CategorieDTO> CategoriesInit1 = new List<CategorieDTO>();
CategoriesInit1 = await categorieService.GetAllCategories();
}
API calls should be awaitable, else it will stuck your UI
You can use this solution as well
private void LoadCategories()
{
var t = Task.Run(() => categorieService.GetAllCategories()()).GetAwaiter();
t.OnCompleted(() =>
{
CategoriesInit1 = t.GetResult();
// you may need to call statehaschanged as well
StateHasChanged();
});
}
I have a requirement as follows, I want to print the screen elements present on the screen to printer. Implementation is done through MVVM. so If I click on print button on the screen it should display a print dialogue and selecting the printer should proceed with printing all the UI elemnts with their data . I have tried with solution present at print WPF visual from viewmodel but its missing the margings and not displaying properly
Also I have another button Print Preview which should display print preview dialogue to see the preiview.
Thanks in advance.
Regards,
Krishna.
In my opinion the printing of the View in an MVVM application is not the responsiblity or concern of the ViewModel. I believe you are better of doing this from the View.
How I've achieved this before is to use a WPF Behavior on a button - I use a Behavior because I'm using DataTemplates for the View and there isn't a 'code behind' file.
The Behavior exposes a DependencyProperty, this is a binding to what is to be printed or contains what is going to be printed.
XAML:
<Button Margin="0,2,5,2"
HorizontalAlignment="Right"
Content="PRINT"
ToolTip="Prints the current report">
<i:Interaction.Behaviors>
<b:ReportPrintClickBehavior Content="{Binding ElementName=SelectedReportContent, Mode=OneWay}" />
</i:Interaction.Behaviors>
</Button>
To reference the Behavior in the XAML you'll need to reference System.Windows.Interactivity, this can be found on NuGet here.
Code-Behind (Behavior):
In this case I'm printing a FlowDocument hosted inside a FlowDocumentReader.
public sealed class ReportPrintClickBehavior : Behavior<Button>
{
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content",
typeof(DependencyObject),
typeof(ReportPrintClickBehavior),
new PropertyMetadata(null));
public DependencyObject Content
{
get { return (DependencyObject)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += OnLoaded;
AssociatedObject.Unloaded += OnUnloaded;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= OnLoaded;
AssociatedObject.Unloaded -= OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs args)
{
AssociatedObject.Click += OnClick;
}
private void OnUnloaded(object sender, RoutedEventArgs args)
{
AssociatedObject.Click -= OnClick;
}
private void OnClick(object sender, RoutedEventArgs args)
{
var flowDocumentReader = Content.GetVisualDescendent<FlowDocumentReader>();
if (flowDocumentReader != null)
{
flowDocumentReader.Print();
}
}
}
Within a XAML user control, the Frame object is null:
this.Frame.Navigate(typeof(FaxPropertiesPage));
How do I navigate between pages with a Windows 8 XAML User Control? I have placed the control within a Callisto Flyout on a XAML page.
The search button below must navigate the user to another XAML page.
I've successfully used the code from app.xaml.cs
Frame frame = Window.Current.Content as Frame;
and then used the standard Navigate code.
There's the nice way and the not-so-nice way:
Both of them start with a navigation service:
public interface INavigationService
{
bool CanGoBack { get; }
void GoBack();
void GoForward();
bool Navigate<T>(object parameter = null);
bool Navigate(Type source, object parameter = null);
void ClearHistory();
event EventHandler<NavigatingCancelEventArgs> Navigating;
}
public class NavigationService : INavigationService
{
private readonly Frame _frame;
public NavigationService(Frame frame)
{
_frame = frame;
frame.Navigating += FrameNavigating;
}
#region INavigationService Members
public void GoBack()
{
_frame.GoBack();
}
public void GoForward()
{
_frame.GoForward();
}
public bool Navigate<T>(object parameter = null)
{
Type type = typeof (T);
return Navigate(type, parameter);
}
So, where do I get the Frame? In App.xaml.cs
protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
// Do not repeat app initialization when already running, just ensure that
// the window is active
if (args.PreviousExecutionState == ApplicationExecutionState.Running)
{
Window.Current.Activate();
return;
}
// Create a Frame to act as the navigation context and navigate to the first page
var rootFrame = new Frame();
if (DesignMode.DesignModeEnabled)
SimpleIoc.Default.Register<INavigationService, DesignTimeNavigationService>();
else
SimpleIoc.Default.Register<INavigationService>(() => new NavigationService(rootFrame));
I'm using MVVM Light here. This makes life easy because all my viewmodels get created using dependency injection and have their services injected into them.
If you're not using something like MVVM Light and rely on code-behind then you can still make this work: Just make the navigation service static
public class NavigationService : INavigationService
{
public static INavigationService Current{
get;set;}
blah blah blah
}
And change App.xaml.cs to:
protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
// Do not repeat app initialization when already running, just ensure that
// the window is active
if (args.PreviousExecutionState == ApplicationExecutionState.Running)
{
Window.Current.Activate();
return;
}
// Create a Frame to act as the navigation context and navigate to the first page
var rootFrame = new Frame();
NavigationService.Current= new NavigationService(rootFrame));
}
And you can then access your main Frame anywhere in the app by saying:
NavigationService.Current.Navigate<MyView>();
simple code ( may not be 100% efficient) is :
Frame frame = new Frame();
frame.Navigate(typeof(ExerciseAddPage)
My code is below: I am seeing that on running the app the loadWidget method gets invoked even when the adminLink is not clicked. This is not want I want, but I'm not sure what is causing the issue. Please advise
public class LoginModule implements EntryPoint {
LoginPopup loginPopup;
private class LoginPopup extends PopupPanel {
public LoginPopup() {
super(true);
}
public void loadWidget(){
System.out.println("I am called 1");
CommonUi cUi = new CommonUi();
//#342 moved code to common area
FormPanel loginForm = cUi.getLoginFormUi();
setWidget(loginForm);
}
}
#Override
public void onModuleLoad() {
//#251 improved login popup ui.
final Anchor adminLink = new Anchor("User Login");
// final Label adminLink = new Label("User Login");
adminLink.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
// Instantiate the popup and show it.
loginPopup = new LoginPopup();
loginPopup.loadWidget();
loginPopup.showRelativeTo(adminLink);
loginPopup.show();
}
});
if(RootPanel.get("admin") !=null)
RootPanel.get("admin").add(adminLink);
}
}
Running Dev Mode, set a breakpoint in that method in your Java IDE, and take a look at the current stack, what code is calling that method. If that is the only code in your app, then this only appears to be invokable from that onClick handlers, so it is a matter of figuring out why that is being invoked.
My application has a menu option to allow the creation of a new account. The menu option's command is bound to a command (NewAccountCommand) in my ViewModel. When the user clicks the option to create a new account, the app displays a "New Account" dialog where the user can enter such data as Name, Address, etc... and then clicks "Ok" to close the dialog and create the new account.
I know my code in the ViewModel is not correct because it creates the "New Account" dialog and calls ShowDialog(). Here is a snippet from the VM:
var modelResult = newAccountDialog.ShowDialog();
if (modelResult == true)
{
//Create the new account
}
how do i avoid creating and showing the dialog from within my VM so I can unit test the VM?
I like the approach explained in this codeproject article:
http://www.codeproject.com/KB/WPF/XAMLDialog.aspx
It basically creates a WPF Dialog control that can be embedded in the visual tree of another window or usercontrol.
It then uses a style trigger that causes the dialog to open up whenever there is content in the dialog.
so in you xaml all you have to do is this(where DialogViewModel is a property in you ViewModel):
<MyControls:Dialog Content = {Binding DialogViewModel}/>
and in you ViewModel you just have to do the following:
DialogViewModel = new MyDialogViewModel();
so in unit testing all you have to do is:
MyViewModel model = new MyViewModel();
model.DialogViewModel = new MyDialogViewModel();
model.DialogViewModel.InputProperty = "Here's my input";
//Assert whatever you want...
I personally create a ICommand property in my ViewModel that sets the DialogViewModel property, so that the user can push a button to get the dialog to open up.
So my ViewModel never calls a dialog it just instantiates a property. The view interprets that and display a dialog box. The beauty behind this is that if you decide to change your view at all and maybe not display a dialog, your ViewModel does not have to change one bit. It pushes all the User interaction code where it should be...in the view. And creating a wpf control allows me to re-use it whenever I need to...
There are many ways to do this, this is one I found to be good for me. :)
In scenarios like this, I typically use events. The model can raise an event to ask for information and anybody can respond to it. The view would listen for the event and display the dialog.
public class MyModel
{
public void DoSomething()
{
var e = new SomeQuestionEventArgs();
OnSomeQuestion(e);
if (e.Handled)
mTheAnswer = e.TheAnswer;
}
private string mTheAnswer;
public string TheAnswer
{
get { return mTheAnswer; }
}
public delegate void SomeQuestionHandler(object sender, SomeQuestionEventArgs e);
public event SomeQuestionHandler SomeQuestion;
protected virtual void OnSomeQuestion(SomeQuestionEventArgs e)
{
if (SomeQuestion == null) return;
SomeQuestion(this, e);
}
}
public class SomeQuestionEventArgs
: EventArgs
{
private bool mHandled = false;
public bool Handled
{
get { return mHandled; }
set { mHandled = value; }
}
private string mTheAnswer;
public string TheAnswer
{
get { return mTheAnswer; }
set { mTheAnswer = value; }
}
}
public class MyView
{
private MyModel mModel;
public MyModel Model
{
get { return mModel; }
set
{
if (mModel != null)
mModel.SomeQuestion -= new MyModel.SomeQuestionHandler(mModel_SomeQuestion);
mModel = value;
if (mModel != null)
mModel.SomeQuestion += new MyModel.SomeQuestionHandler(mModel_SomeQuestion);
}
}
void mModel_SomeQuestion(object sender, SomeQuestionEventArgs e)
{
var dlg = new MyDlg();
if (dlg.ShowDialog() != DialogResult.OK) return;
e.Handled = true;
e.TheAnswer = dlg.TheAnswer;
}
}
The WPF Application Framework (WAF) shows a concrete example how to accomplish this.
The ViewModel sample application shows an Email Client in which you can open the “Email Account Settings” dialog. It uses dependency injection (MEF) and so you are still able to unit test the ViewModel.
Hope this helps.
jbe
There are different approaches to this. One common approach is to use some form of dependency injection to inject a dialog service, and use the service.
This allows any implementation of that service (ie: a different view) to be plugged in at runtime, and does give you some decoupling from the ViewModel to View.