I have a xaml file where I set up a NavigationView with MenuItems. Inside my Project I implemented a basic Navigationservice which lets me switch the frames on Command Trigger:
Navigationservice:
public class NavigationService : INavigationService
{
public void GoBack()
{
var frame = (Frame)Window.Current.Content;
frame.GoBack();
}
public void Navigate(Type sourcePage)
{
var frame = (Frame)Window.Current.Content;
frame.Navigate(sourcePage);
}
public void Navigate(Type sourcePage, object parameter)
{
var frame = (Frame)Window.Current.Content;
frame.Navigate(sourcePage, parameter);
}
public void NavigateScrollViewer(Type sourcePage)
{
//ToDo Inject sourcePage into ScrollViewer of Frame
var frame = (Frame)Window.Current.Content;
var page = frame.CurrentSourcePageType;
}
}
Now an example Command would be this: The RelayCommand is a basic implementation that you find all over the place.
private ICommand _navigateToTextToSpeechView;
public ICommand NavigateToTextToSpeechView
{
get
{
return _navigateToTextToSpeechView =
new RelayCommand((a) =>
{
_navigationService.Navigate(typeof(AudioTextToSpeechView));
//ScrollFrame.Navigate(typeof(AudioHomeViewModel));
});
}
}
Further on I assign the ViewModel inside the code behind file of the view through the DataContext.
<Page
x:Class="ToolBoxApp.Views.AudioHomeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ToolBoxApp.Views"
xmlns:viewmodels="using:ToolBoxApp.ViewModels"
xmlns:mainview="clr-namespace:ToolBoxApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<!--<Page.DataContext>
<viewmodels:AudioHomeViewModel/>
</Page.DataContext>-->
<Grid>
<NavigationView x:Name="navigationViewControl"
IsBackEnabled="true">
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ItemInvoked">
<core:EventTriggerBehavior.Actions>
<core:InvokeCommandAction Command="{Binding NavigateToTextToSpeechView}" />
</core:EventTriggerBehavior.Actions>
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
<NavigationView.MenuItems>
<NavigationViewItem Icon="MusicInfo" Content="Text to Speech"/>
<NavigationViewItem Icon="MusicInfo" Content="Youtube to Mp3"/>
</NavigationView.MenuItems>
<ScrollViewer>
<Frame x:Name="ContentFrame"/>
</ScrollViewer>
</NavigationView>
</Grid>
</Page>
Now I found an answer to how to assign a command to the MenuItems thorugh the Microsoft.Xaml.Behaviors.Uwp.Managed NuGet package but the command gets now triggered for all MenuItems which I dont want. I want to assign different Commands to different MenuItems. How would I be able to achieve this?
I solved it like this:
public ICommand NavigateToTextToSpeechView
{
get
{
return _navigateToTextToSpeechView =
new GenericRelayCommand<NavigationViewItemInvokedEventArgs>(OnItemInvoked);
}
}
public void OnItemInvoked(NavigationViewItemInvokedEventArgs args)
{
string invokedItemName = args.InvokedItem.ToString();
Debug.WriteLine(invokedItemName);
if (invokedItemName.Equals("Text to Speech"))
{
_navigationService.Navigate(typeof(AudioTextToSpeechView));
}
}
Personally I dont like this approach but it works. Just if someone is interested.
If someone coould provide a better solution without string checks than I would be happy. In the long run this function could become pretty big depending on how much NavigationMenuItems I have.
Related
Hi everyone i am a newbie in UWP development, i have searched a lot on the net but I have not found the right way to achieve my goal, what i would like to do is update the ui of my MainPage through the use of a class belonging to UiUpdate class. Here's what I'd like to get, this is my MainPage:
namespace Test_App
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
}
}
This is the relative xaml associated with my MainPage:
<Page
x:Class="Test_App.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Test_App"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<controls:DockPanel>
<controls:DockPanel Visibility="Visible" HorizontalAlignment="Left" >
<Grid>
<TextBlock x:Name="text_one" HorizontalAlignment="Right" Margin="0,50,46,0" VerticalAlignment="Center" FontWeight="Bold" />
<TextBlock x:Name="text_two" HorizontalAlignment="Left" VerticalAlignment="Bottom" FontWeight="Medium" />
</Grid>
</controls:DockPanel>
</Grid>
</Page>
Now through the UiUpdate class, I would like to update my TextBlocks or any other element of my UI, I found something like this on the net:
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync (CoreDispatcherPriority.Normal, () =>
{
// Update texboxt
});
But i can't access in any way in the UiUpdate class the texbox elements of the MainPage i can't do MainPage.text_one .., also i can't find good documentation for what concerns patterns to be applied or other for the realization of my UWP app
How to update item xaml from another class in UWP
For explain this question, you may need to refer UWP mvvm design, and you mentioned UiUpdate most like ViewModel.
For example
<Page.DataContext>
<local:UiUpdate x:Name="ViewModel" />
</Page.DataContext>
<Grid>
<TextBlock
x:Name="text_one"
Margin="0,50,46,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding TextBlockText}" />
<Button VerticalAlignment="Bottom" Click="Button_Click">Update</Button>
</Grid>
Code behind
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private Random random = new Random();
private void Button_Click(object sender, RoutedEventArgs e)
{
ViewModel.TextBlockText = $"Text-----{random.Next(15)}";
}
}
public class UiUpdate : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _textBlcokText;
public string TextBlockText
{
get
{
return _textBlcokText;
}
set
{
_textBlcokText = value;
OnPropertyChanged();
}
}
}
Hi I have a xaml File where I have a NavigationView with NavigatioViewItems. Inside the NavigationView I have a ScrollViewer where I want to inject a view. This is the xaml:
<Page
x:Class="ToolBoxApp.Views.AudioHomeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ToolBoxApp.Views"
xmlns:viewmodels="using:ToolBoxApp.ViewModels"
xmlns:mainview="clr-namespace:ToolBoxApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<!--<Page.DataContext>
<viewmodels:AudioHomeViewModel/>
</Page.DataContext>-->
<Grid>
<NavigationView x:Name="navigationViewControl"
IsBackEnabled="true"
>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ItemInvoked">
<core:EventTriggerBehavior.Actions>
<core:InvokeCommandAction Command="{Binding NavigateToTextToSpeechView}" />
</core:EventTriggerBehavior.Actions>
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
<NavigationView.MenuItems>
<NavigationViewItem Icon="MusicInfo" Content="Text to Speech"/>
<NavigationViewItem Icon="MusicInfo" Content="Youtube to Mp3"/>
</NavigationView.MenuItems>
<ScrollViewer>
<Frame x:Name="ContentFrame"/>
</ScrollViewer>
</NavigationView>
</Grid>
</Page>
So far I have a NavigationService where I can navigate to different views:
public class NavigationService : INavigationService
{
public void GoBack()
{
var frame = (Frame)Window.Current.Content;
frame.GoBack();
}
public void Navigate(Type sourcePage)
{
var frame = (Frame)Window.Current.Content;
frame.Navigate(sourcePage);
}
public void Navigate(Type sourcePage, object parameter)
{
var frame = (Frame)Window.Current.Content;
frame.Navigate(sourcePage, parameter);
}
public void NavigateScrollViewer(Type sourcePage, Type injectPage)
{
var frame = (Frame)Window.Current.Content;
var page = frame.CurrentSourcePageType;
}
}
The NavigatScrollViewer method should implement this in a generic way because the whole app will use it. I will have different Windows like the xaml file above where I want to inject views into the ScrollViewer Frame. Is there a way to get a reference to this ScrollViewer inside code and then setting its Frame to the view I want to specify through the NavigationService?
Any help is appreciated. Thanks
C# UWP MVVM inject a view inside another view
If you have used MVVM design, you need to bind Frame SourcePageType property with ViewModel's page type property. you just need to update this page type property, the Frame content will be update.
Here is sample code that you could refer.
Is there a way to get a reference to this ScrollViewer inside code and then setting its Frame to the view I want to specify through the NavigationService?
the other way is pass ContentFrame instance to NavigationService. At first you need add new frame property for NavigationService class. And then replace your code frame with MyFrame.
public class NavigationService
{
private Frame MyFrame { get; set; }
public NavigationService(Frame frame)
{
MyFrame = frame;
}
}
Usgae
var navService = new NavigationService(ContentFrame);
For above design is referring Windows Template Studio NavigationService, it is complete implementation, and you could find the NavigationService initialization process in the shell page.
ViewModel.Initialize(shellFrame, navigationView, KeyboardAccelerators);
I'm new to WPF so bear with me. I have a WinForms Application that I am trying to redo in WPF. In my current WinForms Application, I stick all my Controls into one Form, and hide/show them based on what buttons are hit, as well as making use of a second form.
My goal: Create different views to switch between smoothly based on what button is hit, instead of hiding Controls or making separate Forms and then hiding those.
I currently have a MainWindow view (My initial launch window), where with a button, I switch to my CreateAccount view. What I am having issues with is, how can I make my button in my CreateAccount go "back" to my MainWindow?
My end goal is to be able to switch between 4 views based off Button clicks.
Here is my MainWindow.xaml
<Window x:Class="MusicPlayer.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MusicPlayer"
xmlns:Views="clr-namespace:MusicPlayer.Views"
xmlns:ViewModels="clr-namespace:MusicPlayer.ViewModels"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate x:Name="CreateAccountTemplate" DataType="{x:Type ViewModels:CreateAccountViewModel}">
<Views:CreateAccountView DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Button x:Name="TestButton" Content="Button" HorizontalAlignment="Left" Margin="164,182,0,0" VerticalAlignment="Top" Height="61" Width="68" Click="CreateAccountView_Clicked"/>
<PasswordBox HorizontalAlignment="Left" Margin="164,284,0,0" VerticalAlignment="Top" Width="120"/>
<ContentPresenter Content="{Binding}"/>
</Grid>
</Window>
My MainWindow.xaml.cs
using System;
using System.Windows;
using MusicPlayer.ViewModels;
namespace MusicPlayer {
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
protected override void OnClosed(EventArgs e) {
base.OnClosed(e);
Application.Current.Shutdown();
} //end of onClosed
private void CreateAccountView_Clicked(object sender, RoutedEventArgs e) {
DataContext = new CreateAccountViewModel();
} //end of CreateAccountView_Clicked
}
}
And here is my CreateAccount.xaml
<UserControl x:Class="MusicPlayer.Views.CreateAccountView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:Views="clr-namespace:MusicPlayer.Views"
xmlns:ViewModels="clr-namespace:MusicPlayer.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
</UserControl.Resources>
<Grid Background="White">
<Button Content="Button" HorizontalAlignment="Left" Margin="276,279,0,0" VerticalAlignment="Top" Height="60" Width="59" Click="Button_Click"/>
</Grid>
</UserControl>
And my CreateAccountView.xaml.cs
using System.Windows;
using System.Windows.Controls;
using MusicPlayer.ViewModels;
namespace MusicPlayer.Views {
public partial class CreateAccountView : UserControl {
//public static readonly DependencyProperty TestMeDependency = DependencyProperty.Register("MyProperty", typeof(string), typeof(CreateAccountView));
public CreateAccountView() {
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e) {
DataContext = new MainWindowViewModel();
}
}
}
It seems to me that your current attempt is on the right track. The main issue with the code you posted is that the CreateAccountView.Button_Click() handler doesn't have access to the DataContext property it should be setting:
private void Button_Click(object sender, RoutedEventArgs e) {
DataContext = new MainWindowViewModel();
}
This DataContext property belongs to the CreateAccountView user control. However, this is not the controlling context for what's being displayed. So changing the value of that DataContext property doesn't have any useful effect. (Indeed, a user control should not set its own DataContext property at all, because doing so discards whatever context the client code using that user control had set.)
There's not enough context to know exactly what the best way for you to do this would be. I don't think it would be possible to provide enough context here on Stack Overflow. The overall architecture will depend on too many little details about your program. But, one way to approach this which I think is a good one would be this:
Create a "main" view model that governs the overall behavior of the app
Create individual view models that relate to different states of the UI
Have the main view model configure the individual view models to switch the current view model as appropriate, given the user input (e.g. clicking buttons)
Translating that into code, looks something like this…
First, the view models:
class MainViewModel : NotifyPropertyChangedBase
{
private object _currentViewModel;
public object CurrentViewModel
{
get => _currentViewModel;
set => _UpdateField(ref _currentViewModel, value);
}
private readonly HomeViewModel _homeViewModel;
private readonly Sub1ViewModel _sub1ViewModel;
private readonly Sub2ViewModel _sub2ViewModel;
public MainViewModel()
{
_sub1ViewModel = new Sub1ViewModel
{
BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
};
_sub2ViewModel = new Sub2ViewModel
{
BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
};
_homeViewModel = new HomeViewModel
{
ShowSub1Command = new DelegateCommand(() => CurrentViewModel = _sub1ViewModel),
ShowSub2Command = new DelegateCommand(() => CurrentViewModel = _sub2ViewModel)
};
CurrentViewModel = _homeViewModel;
}
}
class HomeViewModel : NotifyPropertyChangedBase
{
private ICommand _showSub1Command;
public ICommand ShowSub1Command
{
get => _showSub1Command;
set => _UpdateField(ref _showSub1Command, value);
}
private ICommand _showSub2Command;
public ICommand ShowSub2Command
{
get => _showSub2Command;
set => _UpdateField(ref _showSub2Command, value);
}
}
class Sub1ViewModel : NotifyPropertyChangedBase
{
private ICommand _backCommand;
public ICommand BackCommand
{
get => _backCommand;
set => _UpdateField(ref _backCommand, value);
}
}
class Sub2ViewModel : NotifyPropertyChangedBase
{
private ICommand _backCommand;
public ICommand BackCommand
{
get => _backCommand;
set => _UpdateField(ref _backCommand, value);
}
}
Of course, these view models contain only the implementation details needed to handle the UI switching. In your program, each would also include the stuff specific to each view state that you need.
In my little sample, the "home" view contains a couple of buttons, used to select the individual sub-views available:
<UserControl x:Class="WpfApp1.HomeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Home: "/>
<Button Content="Sub1" Command="{Binding ShowSub1Command}"/>
<Button Content="Sub2" Command="{Binding ShowSub2Command}"/>
</StackPanel>
</UserControl>
The sub views just contain the button required to go back to the home view:
<UserControl x:Class="WpfApp1.Sub1View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sub1 View: "/>
<Button Content="Back" Command="{Binding BackCommand}"/>
</StackPanel>
</UserControl>
<UserControl x:Class="WpfApp1.Sub2View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sub2 View: "/>
<Button Content="Back" Command="{Binding BackCommand}"/>
</StackPanel>
</UserControl>
Finally, the main window sets the main view model, and declares templates to use for each of the specific sub views:
<Window x:Class="WpfApp1.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type l:HomeViewModel}">
<l:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:Sub1ViewModel}">
<l:Sub1View/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:Sub2ViewModel}">
<l:Sub2View/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding CurrentViewModel}"/>
</StackPanel>
</Window>
Importantly, you'll see that none of the view objects include any code-behind. It's not necessary when you approach the problem this way, at least not for the purpose of controlling basic behaviors in the code. (You may still wind up with code-behind for view objects, but this will usually only be for the purpose of implementing specific user-interface behaviors unique to that view object, not for dealing with the view model state.)
Using this approach, you let WPF do as much of the heavy-lifting as possible. It also decouples all of the view model objects from each other. There's a clear hierarchy: only the top-level "main" view model even knows about the other view models. This allows the sub-view models ("home", "sub1", and "sub2") to be reused as necessary in other scenarios without any modification or special-case handling within them.
Here are the helper classes I used above:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
class DelegateCommand : ICommand
{
private readonly Action _execute;
public DelegateCommand(Action execute)
{
_execute = execute;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged;
#pragma warning restore
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _execute();
}
What I'm attempting to do:
Create a custom page control that consumers can use just like the UWP page, but, that also displays it's own custom content along side the consumers content.
What have I tried:
Creating a new Control, inheriting from Page
Creating a templated control that inherits from page
Creating a control that contains a page
Setting the ContentProperty attribute and binding to it in my custom page
What is the problem?
When I attempt to create a control that has both a xaml and xaml.cs file, that inherits from Page I get InvalidCastExceptions on random controls inside the subclassed control.
Example:
TestPage.xaml
<Page
x:Class="ControlSandbox.TestPage"
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:uwp_toolkit="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Content>
<ContentPresenter Content="{Binding}" />
</Page.Content>
<Page.BottomAppBar>
<AppBar Background="Transparent" x:Name="appbar" IsOpen="True">
<uwp_toolkit:InAppNotification x:Name="note">
<StackPanel>
<TextBlock>HEADER!</TextBlock>
<TextBlock>Message</TextBlock>
<StackPanel Orientation="Horizontal">
<Button>OK</Button>
<Button>Cancel?</Button>
</StackPanel>
</StackPanel>
</uwp_toolkit:InAppNotification>
</AppBar>
</Page.BottomAppBar>
</Page>
TestPage.xaml.cs
public partial class TestPage : Page
{
public TestPage()
{
this.InitializeComponent();
}
public void ShowNotification()
{
appbar.IsOpen = true;
note.Show();
}
}
MainPage.xaml
<controlsandbox:TestPage
xmlns:controlsandbox="using:ControlSandbox" x:Class="ControlSandbox.MainPage"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Button Click="Button_Click">SHOW NOTIFICATION</Button>
</Grid>
</controlsandbox:TestPage>
MainPage.xaml.cs
public partial class MainPage : TestPage
{
public MainPage()
{
this.InitializeComponent();
}
private void Button_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
this.ShowNotification();
}
}
The above code results in an InvalidCastException and for the life of me I can't find the problem.
System.InvalidCastException: 'Unable to cast object of type
'Windows.UI.Xaml.Controls.AppBar' to type
'Windows.UI.Xaml.Controls.Button'.'
Now if I do the same exact code, but all in the MainPage.xaml instead of in the TestPage.xaml everything works as expected
Update
So I believe this is a bug in the platform. Here is a demo I did of the issue. Please prove me wrong because this would be a real limitation https://github.com/DotNetRussell/UWP_Page_Inheritance_Bug
Update
I added the changes for the answer below. It seems that when I create a normal templated control and put it on a vanilla uwp page, it works fine. However, when I create a templated Page, it ignores my template.
Update
I think this is a bug in the platform. I opened an issue up on github https://github.com/microsoft/microsoft-ui-xaml/issues/1075
The problem is that if create custom base page with Xaml, it will be mandatory converted to the subpage's content, if the sub-page contained controls different with Base page will throw exception. And the better way is create base class without xaml and add the base page content in the code behind. For more code please refer the following .
public class BasePage : Page
{
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.BottomAppBar = new AppBar()
{
Background = new SolidColorBrush(Colors.Transparent),
IsOpen = false,
Content = new InAppNotification
{
Content = new StackPanel
{
Children =
{
new TextBlock{ Text = "HEADER!"},
new TextBlock{Text = "Message"},
new StackPanel
{
Orientation = Orientation.Horizontal,
Children=
{
new Button{Content = "ok"},
new Button {Content = "cancel"}
}
}
}
}
}
};
base.OnNavigatedTo(e);
}
public void ShowNotification()
{
this.BottomAppBar.IsOpen = true;
InAppNotification note = this.BottomAppBar.Content as InAppNotification;
if (note != null)
note.Show();
}
}
Usage
public sealed partial class MainPage : BasePage
{
public MainPage()
{
this.InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ShowNotification();
}
}
Xaml
<local:BasePage
x:Class="CustomPage.MainPage"
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:CustomPage"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
>
<Grid>
<Button Click="Button_Click" Content="ClikMe" />
</Grid>
</local:BasePage>
Create a Templated Control (Project->Add New Item->Templated Control in Visual Studio):
public sealed class CustomPage : Page
{
private AppBar appbar;
private InAppNotification note;
public CustomPage()
{
this.DefaultStyleKey = typeof(CustomPage);
}
protected override void OnApplyTemplate()
{
appbar = GetTemplateChild("appbar") as AppBar;
note = GetTemplateChild("note") as InAppNotification;
}
public void ShowNotification()
{
if (appbar != null)
appbar.IsOpen = true;
note?.Show();
}
}
...and define a custom Style in Themes/Generic.xaml:
<Style TargetType="local:CustomPage">
<Setter Property="BottomAppBar">
<Setter.Value>
<AppBar Background="Transparent" x:Name="appbar" IsOpen="True">
<uwp_toolkit:InAppNotification x:Name="note">
<StackPanel>
<TextBlock>HEADER!</TextBlock>
<TextBlock>Message</TextBlock>
<StackPanel Orientation="Horizontal">
<Button>OK</Button>
<Button>Cancel?</Button>
</StackPanel>
</StackPanel>
</uwp_toolkit:InAppNotification>
</AppBar>
</Setter.Value>
</Setter>
</Style>
There is .xaml.cs file for the CustomPage base class.
Edit: Since the BottomAppBar is not part of the template, you need to wait to access the elements in it until they have actually been created. Just do this in the method:
public sealed class CustomPage : Page
{
public CustomPage()
{
this.DefaultStyleKey = typeof(CustomPage);
}
public void ShowNotification()
{
AppBar appBar = this.BottomAppBar;
appBar.IsOpen = true;
InAppNotification note = appBar.Content as InAppNotification;
if(note != null)
note.Show();
}
}
Im just starting out with MVVM and at the moment still find alot of things confusing.
So I am trying to keep things as simple as I can at the moment.
I am trying to write code for a custom image which later will be able to be placed on a canvas control by a user at runtime. I'm trying to use MVVM so that I will be able to save and reload the content on a canvas.
I have created a model class called CustomImage with the following code:
namespace StoryboardToolMvvm
{
public class CustomImage
{
public Uri imageLocation { get; set; }
public BitmapImage bitmapImage { get; set; }
}
}
I have a modelview class as follows:
namespace StoryboardToolMvvm
{
class CustomImageViewModel : ViewModelBase
{
private CustomImage _customImage;
private ObservableCollection<CustomImage> _customImages;
private ICommand _SubmitCommand;
public CustomImage CustomImage
{
get { return _customImage; }
set
{
_customImage = value;
NotifyPropertyChanged("CustomImage");
}
}
public ObservableCollection<CustomImage> CustomImages
{
get { return _customImages; }
set
{
_customImages = value;
NotifyPropertyChanged("CustomImages");
}
}
public ICommand SubmitCommand
{
get
{
if (_SubmitCommand == null)
{
_SubmitCommand = new RelayCommand(param => this.Submit(), null);
}
return _SubmitCommand;
}
}
public CustomImageViewModel()
{
CustomImage = new CustomImage();
CustomImages = new ObservableCollection<CustomImage>();
CustomImages.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(CustomImages_CollectionChanged);
}
private void CustomImages_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("CustomImages");
}
private void Submit()
{
CustomImage.imageLocation = new Uri(#"H:\My Pictures\whale.png");
CustomImage.bitmapImage = new BitmapImage(CustomImage.imageLocation);
CustomImages.Add(CustomImage);
CustomImage = new CustomImage();
}
}
}
And a view class:
<UserControl x:Class="StoryboardToolMvvm.CustomImageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:viewmodel="clr-namespace:StoryboardToolMvvm"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<viewmodel:CustomImageViewModel x:Key="CustomImageViewModel"/>
</UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource CustomImageViewModel}}">
<Image Source="{Binding CustomImage.bitmapImage, Mode=TwoWay}" Width="150" Height="150" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="75,50,0,0" />
<Button Content="Submit" Command="{Binding SubmitCommand}" Width="100" Height="50" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,20" />
</Grid>
</UserControl>
I add this view to my MainWindow.xaml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StoryboardToolMvvm" x:Class="StoryboardToolMvvm.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:CustomImageView HorizontalAlignment="Left" Height="100" Margin="181,110,0,0" VerticalAlignment="Top" Width="100"/>
</Grid>
</Window>
I am very unsure as to whether I am on the right lines here with a MVVM pattern so any comments would be much appreciated. Also when Submit is pressed I would have expected my image to load but this does not happen can anyone advise as to why?
Many Thanks in advance..
As far as my understanding of MVVM and your question goes, I have one main comment about your code.
I think your CustomImage is actually both Model and ViewModel layer, and you should split it in two :
the Model, which would contain the path itself ;
the ViewModel, which contain the BitmapImage and initialize it from the Model and constructing time.
The path is the mere data used for saving, and it fits the Model, whereas the BitmapImage is how the data is shown and should be constructed in the ViewModel.
One advantage is that now, your BitmapImage gets its own NotifyPropertyChanged call at setting time, and you won't have anymore problem or a View part directly bound to the Model.
As for your CustomImageViewModel, this looks like more of a MainViewModel-ish thing. You can still use this to store the ViewModels.