C# UWP MVVM inject a view inside another view - c#

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);

Related

C# XAML Set Commands for different MenuItems inside a NavigationView

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.

Creating a Subclass of Page in UWP

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();
}
}

How to reuse WPF custome window

I have created custome window (titlebar, min/max/ext buttons, own border for window manipulation and lots of styles and triggers).
There are 5 methods defined (which i would like to override):
From window markup:
SourceInitialized="Window_SourceInitialized"
Closing="Window_Closing"
From Titlebar buttons:
Exit_Click()
Max_Click()
Min_Click()
And at last I have DockPanel
<DockPanel Name="ClientArea"/>
In which I want to put my content
I have tried to add content from code:
BaseWindow editInterfaceWindow = new BaseWindow() { Owner = this };
editInterfaceWindow.DataContext = new EditInterface();
editInterfaceWindow.ShowDialog();
But this way some bindings stoped working and inside editInterfaceWindow I cant create another window this way because of Owner = this. There are also some problems with InitializeComponent() in constructor.
And ListView inside EditInterface UserControl <ListView Name="LBAvaliable" ItemsSource="{Binding AvaliableFaces, UpdateSourceTrigger=PropertyChanged}"> is not visible in code as LBAvaliable.
I have used that window few times, filling ClientArea with content by hand.
How should I create other windows, so that I can just inherit it or just define binding? So my XAML for every single window does not take ~1000 lines of code.
In the past I've used MVVMCross Framework and we never had to worry about this ourselves. Though this is not the best, here's an idea on what you can do.
Create a view model that can be overridden for your user control.
Set data templates.
Programmatically change the view model for your user control's main content and let data templates do the work for the UI.
View Model: Pre-defined 3 button actions ready for you to set/override.
public class MainUCViewModel : ViewModelBase
{
private Action<object> btnACommand;
private Action<object> btnBCommand;
private Action<object> btnCCommand;
private object ccVM;
public ViewModelBase CCVM
{
get { return this.ccVM; }
set
{
this.ccVM = value;
OnPropertyChanged(); // Notify View
}
}
public MainUCViewModel()
{
}
public RelayCommand BtnACommand
{
get { return new RelayCommand(btnACommand); }
}
public RelayCommand BtnBCommand
{
get { return new RelayCommand(btnBCommand); }
}
public RelayCommand BtnCCommand
{
get { return new RelayCommand(btnCCommand); }
}
public void SetBtnACommand(Action<object> action)
{
this.btnACommand = action;
}
public void SetBtnBCommand(Action<object> action)
{
this.btnBCommand = action;
}
public void SetBtnCCommand(Action<object> action)
{
this.btnCCommand = action;
}
}
View:
<UserControl x:Class="WpfApplication1.Views.UserControls.MainUC"
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="500" d:DesignWidth="750">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="45" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding BtnACommand}" Width="100">
<TextBlock>A</TextBlock>
</Button>
<Rectangle Width="15" />
<Button Command="{Binding BtnBCommand}" Width="100">
<TextBlock>B</TextBlock>
</Button>
<Rectangle Width="15" />
<Button Command="{Binding BtnCCommand}" Width="100">
<TextBlock>C</TextBlock>
</Button>
</StackPanel>
</Grid>
<Grid Grid.Row="1">
<ContentControl x:Name="CCMain" Content="{Binding CCVM}"/>
</Grid>
</Grid>
</UserControl>
Look at Thinking with MVVM: Data Templates + ContentControl. Simply define the data template for your view model.
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModel:GeneralSettingsViewModel}">
<View:GeneralSettingsView/>
</DataTemplate
<DataTemplate DataType="{x:Type ViewModel:AdvancedSettingsViewModel}">
<View:AdvancedSettingsView/>
</DataTemplate>
</Window.Resources>
What I’m saying here is that GeneralSettingsViewModel should be
rendered using a GeneralSettingsView. That’s exactly what we need !
Because the Views are created using a DataTemplate, we do not need to
setup the DataContext, it will be automatically registered to the
templated object, the ViewModel.
There are two main approaches to your problem:
Inherited windows
Configurable windows
For approach 1, design your window and make the methods overrideable:
In base window xaml, assign the handlers and everything you want:
<Window x:Class="WpfTests.MainWindow"
...
SourceInitialized="Window_SourceInitialized">
In base window, define the handlers as protected virtual (or abstract, if you like to enforce their implementation)
public partial class MainWindow : Window
{
// ...
protected virtual void Window_SourceInitialized(object sender, EventArgs e)
{
}
// ...
}
Create derived windows
public class ExWindow : MainWindow
{
protected override void Window_SourceInitialized(object sender, EventArgs e)
{
// specialized code here
}
}
Change App.xaml to use Startup instead of StartupUri
<Application x:Class="WpfTests.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="Application_Startup">
And manually create your first window, chosing one of the inherited window classes
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
var window = new ExWindow();
window.Show();
}
}
The second approach - configurable windows - follows the same principle as a good user control design: The window/control properties are controlled by the creator instead of being controlled by the window/control itself.
So, instead of defining some event handler within the window code, just leave this exercise to the user, who hopefully knows what the window should do:
public partial class MainWindow : Window
{
// I don't care for SourceInitialized (also remove it from XAML)
}
In App.xaml or wherever a window is created:
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
var window = new MainWindow();
window.SourceInitialized += window_SourceInitialized;
window.Show();
}
void window_SourceInitialized(object sender, EventArgs e)
{
var window = sender as MainWindow;
// I know how to handle this event for this window instance
}
}

How to add a use UserControl containing List in Windows 8.1 App?

I want add a list in UserControl and its values are coming from Json. So how can I bind those values into UserControl from another page and also I want to add a SelectionChanged Event for it.
The scenario is as below:
I have a xaml page containing an image plugin(image). On clicking on Image I want to open UserControl containing list values( for e.g. Take Photo, Select from Library,Record Video and Cancel). Once the user selects the item based on that different method are called.
I was achieving the same approach using CustomMessageBox in Windows Phone 8 but that is not available and I may need to use UserControl to achieve this.
Please guide me for this.
Update
What I have tried so far.
UserControl.Xaml
<UserControl
x:Class="TestApp.Resources.CameraPluginOptions"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyCouncilServices.Resources"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<ListView x:Name="CameraPlugin"
ItemContainerStyle="{StaticResource GenericListViewContainerStyle}"
ItemTemplate="{StaticResource EvidenceListItemTemplate}"
ItemSource="{Binding ItemSource}"
/>
</Grid>
UserControl.xaml.cs
public sealed partial class CameraPluginOptions : UserControl
{
public event EventHandler SelectionChanged;
public List<SUBPARAM> ItemSource
{
get { return (List<SUBPARAM>)GetValue(evidenceItemSourceproperty); }
set { SetValue(evidenceItemSourceproperty, value); }
}
public static readonly DependencyProperty evidenceItemSourceproperty =
DependencyProperty.Register("ItemSource", typeof(List<SUBPARAM>),
typeof(CameraPluginOptions), new PropertyMetadata(0));
public SUBPARAM SelectedOption
{
get { return (SUBPARAM)GetValue(selectedoptionproperty);}
set { SetValue(selectedoptionproperty,value); }
}
public static readonly DependencyProperty selectedoptionproperty =
DependencyProperty.Register("SelectedOption", typeof(SUBPARAM),
typeof(CameraPluginOptions), new PropertyMetadata(0));
private void PluginSelectionChanged(object sender,SelectionChangedEventArgs e)
{
if(SelectionChanged !=null)
SelectionChanged(this,new EventArgs());
}
public CameraPluginOptions()
{
this.InitializeComponent();
}
}
The Page from which I want to use UserControl
TestUserControlPage.xaml.cs
PluginControl.DataContext = new EvidenceSource(){ItemSource=MCSManager.Instance.currentClientParams.EVIDENCE_MENU.SUB_PARAMS};
PluginControl.SelectionChanged += MediaListSelectionChanged;
TestUSerControl.xaml where I am using UserControl
<Grid x:Name="imagePlugin"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Height="400"
Width="600"
Visibility="Collapsed"
xmlns:local="using:TestApp.Resources">
<local:CameraPluginOptions x:Name="PluginControl"
/>
What I actually want to do is bind the list which I get from Json into UserControl ListBox.Itemsource and then I want to have SelectionChanged Event on same page from which UserControl is called. When a user selects an Item particular action should take place.

Creating a custom image class in WPF following MVVM pattern

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.

Categories

Resources