I spent far too long today trying to figure out why my WPF application seemed to be taking so long to load windows that previously loaded much quicker. It turned out that the cause of the problem was an image control with an invalid source uri (for whatever reason, we hadn't noticed the images weren't being rendered).
Whilst the actual code was a lot more involved, (the images spanned many dlls) I can easily reproduce the problem below.
public partial class MainWindow : Window
{
public List<object> Items { get; private set; }
public MainWindow()
{
Items = Enumerable.Repeat(new object(), 250).ToList();
DataContext = this;
InitializeComponent();
}
}
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="icon.png" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
When you replace the source with something that doesn't exist, instead of getting an exception or at least something in the output window all you get is a very long delay before the application starts. Clearly, in this simple example it is easy to see what's wrong, but not so in a large project.
I don't want to get caught by this again, is there anyway to catch these kind of errors? why am I not seeing anything in the output window of a debug build?
EDIT : So it turns out I can do something like this
public partial class App : Application
{
public App()
{
EventManager.RegisterClassHandler(typeof(Image), Image.ImageFailedEvent,
new EventHandler<ExceptionRoutedEventArgs>(OnImageFailed));
}
void OnImageFailed(object sender, ExceptionRoutedEventArgs e)
{
throw new Exception("Image Failed! : " + e.ErrorException.Message, e.ErrorException);
}
}
I'm not overly happy with this though as there could be other controls that may suffer from the same issue and will go undetected until a handler like above is added.
Related
I tried MAUI and MVVM with an ObservableCollection.
Created new MAUI project
Added ViewModel with BindingContext
Bound StackLayout to an ObservableCollection
Added Item to the Collection on Button click
Nothing changes. "Foo" and "Bar" still here although a third element is added on button click.
My project:
Replaced first label in the (default) new project with
<StackLayout
Grid.Row="0"
BindableLayout.ItemsSource="{Binding Foo}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label Text="{Binding .}"/>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
Added
<ContentPage.BindingContext>
<local:MainPageViewModel/>
</ContentPage.BindingContext>
ViewModel:
public class MainPageViewModel
{
public ObservableCollection<string> Foo { get; } = new() { "Foo", "Bar" };
}
Added in the button click (added as first element to avoid missing changes because of UI height problems, but nothing changes when I try Add() instead):
(this.BindingContext as MainPageViewModel).Foo.Insert(0, "Baz");
Is there any info if ObservableCollections should work already?
Or (coming from WPF) is there something I have to change for MAUI/Xamarin?
I've tried to reproduce what you're seeing and I 100% can. The initial value shows up, but not when adding something. Inspecting the Live Visual Tree though, we see that the Labels do actually get added. The ObservableCollection works, it seems the layout just doesn't update.
Even with a call to force the layout on the StackLayout it won't update to it seems, so it looks like the layout isn't working properly at this point.
Looking at the roadmap there is still some layout work to do, it will probably be fixed for preview 7.
Here is a solution that works.
I suggest to create a model.
Your model should implement ObservableObject.
Public class Post: ObservableObject
{
private string title= String.Empty;
public string Title
{
get
{
return this.title;
}
set
{
SetProperty(ref title, value);
}
}
}
Your view model should implement ObservableObject
public partial class PostViewModel : ObservableObject
{
[ObservableProperty]
ObservableCollection<Post> posts;
}
Afterwards, any modification on the property Title will render the ui accordingly.
(Tested on Visual Studio 17.3.6)
More information can be found here.
In part of a XAML code maximum and minimum values are set as follows:
<WindowsFormsHost>
<wf:NumericUpDown Maximum="12000" Minimum="120" x:Name="MyNumericUpDown" TextAlign="Right"/>
</WindowsFormsHost>
And in the same C# program inside a class I have the following:
namespace MyNameSpace
{
public class MyClass
{
public int max { get; } = 12000;
public int min { get; } = 120;
...
}
}
Is it possible to set the Maximum and Minimum values of NumericUpDown by using the class properties instead of hardcoding them? So that of I change class property values the XAML values autonomically updated.
Yup, it's possible, and even what I'd consider to be fundamental in WPF development. It's called data binding. I'll be implementing a simple one way data binding here which binds to a property on the code behind file (i.e MainView.xaml binds to MainView.xaml.cs), but you can bind to any class you'd like, this then forms the fundamentals of MVVM.
Xaml:
<Window x:Class="MainView"
Title="MainView"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<!-- Snip -->
</Window>
The important part is DataContext="{Binding RelativeSource={RelativeSource Self}}. Right here we're telling the window to bind to itself (i.e the code behind file). This is required for anything to work. You can use this Q&A to see how to bind to something else.
Continuing on:
<wf:NumericUpDown Maximum="{Binding Max}" Minimum="{Binding Min}" />
Here we're telling the NumericUpDown component to get its Maximum and Minimum values from the data context. Now, one caveat, you can only bind to properties and nothing else. So, if you ever run into a problem where your bindings aren't working, check to see if you're binding to a property or not. Here's the code of code behind file
public class MainWindow
{
public int Max { get; } = 12000;
public int Min { get; } = 120;
public MainWindow()
{
InitializeComponent();
}
}
This is a one way data binding, i.e the XAML can only read these values. For things like an input box, you'll need to use TwoWay bindings. And if anything changes those values (currently impossible, but may be in the future), then you need to implement INotifyPropertyChanged
P.S: No guarantees for the code. I just typed it out in this text box without any auto complete and the last time I did WPF is quite a while ago. Just leave a comment if something doesn't work and I'll try my best to fix it
My app is blocking when i want to navigate to certain view, so i want to show a load icon. The problem is the icon never shows when expected. The view changes and i never see the icon, but if i go back the icon is there.
I tried using an async task to do the navigation, but the navigation doesn't work in a task, i guess.
Any suggestions or ideas?
XAML Code:
<UserControl xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:fa="http://schemas.fontawesome.io/icons/">
<Grid>
<Listbox ItemsSource={Binding ItemsList}>
</Listbox>
<Canvas Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="-550, 180, 0, 0">
<fa:ImageAwesome Visibility="{Binding LoadingIcon, UpdateSourceTrigger=PropertyChanged}"
Icon="Spinner"
Spin="True"
Canvas.Left="56"
Canvas.Top="-17" />
</Canvas>
</Grid>
<UserControl.InputBindings>
<KeyBinding Key="Enter" Command="{Binding NavigateToMainMenuCommand}"/>
</UserControl.InputBindings>
</UserControl>
ViewModel:
public class LoginViewModel: ViewModelBase, INotifyPropertyChanged, INavigationAware
{
public InicioContableViewModel(IRegionManager regionManager,
IEventAggregator eventAggregator)
{
_regionManager = regionManager;
_eventAggregator = eventAggregator;
NavigateToMainMenuCommand = new DelegateCommand(NavigateToMainMenu);
LoadingIcon = Visibility.Hidden;
}
public DelegateCommand NavigateToMainMenuCommand { get; private set; }
private Visibility loadingIcon;
public Visibility LoadingIcon
{
get
{
return loadingIcon;
}
set
{
SetProperty(ref loadingIcon, value, nameof(LoadingIcon));
NotifyPropertyChanged(nameof(LoadingIcon));
}
}
private void NavigateToMainMenu()
{
LoadingIcon = Visibility.Visible;
string mainUri = nameof(SomeRoute.MainMenu);
_regionManager.RequestNavigate(Regions.MainRegion, mainUri);
}
}
In your Icon's properties, try to set Build Action to Resource.
WPF uses the UI thread to do things like handle mouse movement, change icons when you tell it to and to switch out one view for another. All one thread.
When that thread is busy doing something then nothing is likely to change in your UI until it's finished.
Hence, if you tell it to make something visible AND navigate, you can well find all that happens visually is the navigation. Because it has no time to show your icon.
Try making your method async and giving it a little time:
private async void NavigateToMainMenu()
{
LoadingIcon = Visibility.Visible;
await Task.Delay(60);
string mainUri = nameof(SomeRoute.MainMenu);
_regionManager.RequestNavigate(Regions.MainRegion, mainUri);
}
That task.delay should give it enough time to redraw a bit of ui.
If you need a loading icon and the thing you are navigating to is blocking then I suspect you have other problems. It's likely that whatever this new view is doing to initialise should be asynchronous. Maybe with data access etc on background threads.
You may most likely make use of the functions:- InvalidateVisual() or UpdateLayout(). Those will force to redraw and may resolve your icon visibility issues.
My app is blocking when i want to navigate to certain view, so i want to show a load icon.
If the app is blocking, you cannot show the icon, because, you know, the app is blocked. You have to remove the blocking, that is, make navigation itself quick, and do whatever initialization asynchronously. The view you're navigating to may need stuff from a database or connection to a usb device, but it won't need it immediately. You can still query the database or whatever in the background after the view is shown.
show wait indicator-> navigate -> creates view model -> starts initializion task -> initialization completes -> hide wait indicator
I have a UWP app, with two Pages: MainPage and EventPage. On both of these there is a splitView, and at the top of this a GridView with two buttons - one to navigate to MainPage, and one to navigate to EventPage. The XAML for the buttons looks like this:
<Button Content="Browse by system"
HorizontalAlignment="Center"
Margin="0,0,0,0"
VerticalAlignment="Top"
Width="250"
Click="SystemButtonClick"/>
However, when i press the "Browse by system" button, the app crashes. Here is the constructor for the page:
public EventPage()
{
this.InitializeComponent();
var systemList = SystemClass.GetSystems();
systemList.Sort();
Systems = new ObservableCollection<string> (systemList);
}
It passes the constructor fine, and instead crashes when exiting the eventhandler for the button click:
private void SystemButtonClick(object sender, RoutedEventArgs e)
{
this.Frame.Navigate(typeof(EventPage));
}
I've modeled this after the Peer-to-peer navigation tutorial from microsoft, and can't find any significant differences. The only similar issue I could find here was this, but that seemed to be due to the Template10 package which I'm not using.
When the crash does occur, it goes to the App.g.i.cs file and complains that the debugger isn't configured to debug this unhandled exception.
Does anyone have any idea why this might be happening?
EDIT: To add, if I click the button to move to the current page, it reloads fine. I also just tried starting the program to the EventPage, which also prompted a crash.
EDIT2: After some further testing, it seems I've located the source of the crash, though I don't understand it.
At the beginning of my EventPage class, I have a few variables:
public sealed partial class EventPage : Page
{
private ObservableCollection<EventBin> EventCollection;
private ObservableCollection<String> Systems;
public EventPage()
{
this.InitializeComponent();
Systems = SystemClass.GetSystems();
}
It seems that the crass occurs when I assign the Systems variable. This doesn't occur with my identical operation for the other page, with a different variable. The only difference being that in the MainPage, it's an ObservableCollection of a custom class rather than of strings.
If I re-initialize the System variable like this:
var Systems = SystemClass.GetSystems();
It runs, but doesn't connect to my bindings in the XAML.
So, it turned out that the issue was that I had bound a custom type to the list in my data template, and then tried feeding it plain strings.
<DataTemplate x:Key="System_DefaultItemTemplate"
x:DataType="local:SystemClass">
<StackPanel>
<TextBlock Text="{x:Bind Name}"/>
<TextBlock Text="{x:Bind Site}"/>
</StackPanel>
</DataTemplate>
Since the strings obviously didn't have any Name or Site properties, it crashed.
What I've got is basicly two classes Plugin and PluginLauncher
Plugin is an abstract class that implements some functions to make a class a Plugin for my PluginLauncher class.
PluginLauncher is a class that holds a collection (SortedDictionary) including some helper functions to start, stop, restart all or a specific Plugin.
It also loads all plugins on initialisation. Each plugin can be a .exe or .dll with a class the inherits from Plugin.
An AppDomain is created for each plugin and communication is also setup for each Plugin (through a simple IPC messaging through sockets) (still has to be implemented)
I want to have a very, VERY basic GUI implementation that just has a list of all loaded Plugins, noting the plugin name, its state (which can be running, stopping, stopped, stoppedprematurely (an Enum)) and a button per plugin to start, stop or restart it.
I know I can add this functionality programmaticaly by just placing the elements on the GUI and calculating each X/Y location etc. but I am sure WPF has some pre-made 'functionalities' for this. But I am quite new to WPF and have no clue where to start looking.
A simple note: I am limited to .net 3.5 (or lower) thus no 4.0 elements.
I've included a very simple (hooray mspaint skills) example of what I had in mind.
The plugin nature of your app has little bearing on the mechanics of how you achieve this. Essentially, you need a collection of view models. Each item in that collection represents a plugin (but it could equally represent a customer or a chicken drumstick). You then bind an ItemsControl to this collection and define a template for how the item should be rendered.
Here is some pseudo-code to get you on your way:
public class PluginViewModel : ViewModel
{
public string Name { get; }
public PluginState State { get; private set; }
public ICommand StartCommand { get; }
public ICommand StopCommand { get; }
public ICommand RestartCommand { get; }
}
public class PluginLauncherViewModel : ViewModel
{
// use an ObservableCollection<PluginViewModel> to store your plugin view models
public ICollection<PluginViewModel> Plugins { get; }
}
<ScrollViewer>
<ItemsControl ItemsSource="{Binding Plugins}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<UniformGrid Rows="1">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Status}"/>
<Button Command="{Binding StartCommand}">Start</Button>
<Button Command="{Binding StopCommand}">Stop</Button>
<Button Command="{Binding RestartCommand}">Restart</Button>
</UniformGrid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
Some problems you will no doubt bump into:
the DataContext of the outer XAML (ie. the ScrollViewer in my above example) must be an instance of PluginLauncherViewModel. How you wire this up is up to you, and there are varied options. Start with something simple like setting it in your code-behind.
ViewModel is a base class for all view models. See here for an example.
Your implementation of ICommand should be MVVM friendly. See here for an example.
For the simplest approach you might consider the Table element. For a more fine-grained control I'd recommend you using a Grid.