Load image from viewmodel into view with Caliburn.Micro - c#

I'm trying to let the user load an image in the view.
My main view:
<UserControl x:Class="TestApplication.Views.MainView"
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">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0.3*" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
Stretch="Uniform"
x:Name="Preview">
<Image.Source>
<BitmapImage UriSource="pack://application:,,,/Resources/placeholder.jpg" />
</Image.Source>
</Image>
<ContentControl
Grid.Column="1"
x:Name="ImageManagement" />
</Grid>
</UserControl>
My main viewmodel:
namespace TestApplication.ViewModels
{
using System;
using System.ComponentModel.Composition;
using System.Windows.Controls;
using Caliburn.Micro;
using PropertyChanged;
using TestApplication.Events;
[ImplementPropertyChanged]
[Export(typeof(MainViewModel))]
public class MainViewModel : PropertyChangedBase, IHandle<FileSelectedEvent>
{
private readonly IEventAggregator events;
[ImportingConstructor]
public MainViewModel(IEventAggregator events)
{
this.events = events;
this.events.Subscribe(this);
this.ImageManagement = new ImageManagementViewModel(events);
}
public ImageManagementViewModel ImageManagement { get; set; }
public Image Preview { get; set; }
public void Handle(FileSelectedEvent message)
{
// load the selected image
}
}
}
The image placeholder isn't displayed, and snoop doesn't even see it.
Furthermore, when loading an image by instantiating a BitmapImage and setting it to the Preview.Source in the Handle method... The Preview property is null, and if I initialize it first, it's never displayed either.
The PropertyChanged is handled through Fody.

Thanks to #Will and #mvermef, I found my answer.
The placeholder URL was not invalid, but it does work without the pack:// part. I like it, though, because then it's explicit that it's a resource and not a file.
The real problem was that, indeed, Caliburn.Micro wasn't actually binding to the control, but to the Source property. Switching to this solved the issue:
<Image
Grid.Column="0"
Stretch="Uniform"
Source="{Binding PreviewUrl}" />
and the viewmodel:
[ImportingConstructor]
public MainViewModel(IEventAggregator events)
{
this.events = events;
this.events.Subscribe(this);
this.ImageManagement = new ImageManagementViewModel(events);
this.PreviewUrl = "pack://application:,,,/Resources/placeholder.jpg";
}
public String PreviewUrl { get; set; }
public void Handle(FileSelectedEvent message)
{
this.PreviewUrl = message.FilePath;
}

Related

MVVM Hierarchical Navigation tutorial StackOverFlow exception

I'm trying to reproduce a MVVM tutorial for WPF but applying it to UWP. But I've done everything in the tutorial I believe right the exact same code shown at the tutorial.
But when I ran the code I kept getting a StackOverflowException which is caused because the MainPageView keeps initializing again and again, until the exception is thrown.
The thing is I'm kinda knew at MVVM and I wish to master it, so can somebody please explain me why am I getting this?
I'll leave the code of each one of my classes and views.
This is my MainPageView.Xaml:
<Page
x:Class="MVVMHierarchiesDemo.MainPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVMHierarchiesDemo"
xmlns:views="using:MVVMHierarchiesDemo.Views"
xmlns:viewmodel="using:MVVMHierarchiesDemo.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<!--Anytime the current view model is set to an instance of a CustomerListViewModel,
it will render out a CustomerListView with the ViewModel is hooked up. It’s an order ViewModel,
it'll render out OrderView and so on.
We now need a ViewModel that has a CurrentViewModel property and some logic and commanding
to be able to switch the current reference of ViewModel inside the property.-->
<Page.DataContext>
<local:MainPageView/>
</Page.DataContext>
<Page.Resources>
<DataTemplate x:Key="CustomerTemplate" x:DataType="viewmodel:CustomerListViewModel">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate x:Key="OrderTemplate" x:DataType="viewmodel:OrderViewModel">
<views:OrderView/>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid x:Name="NavBar"
Grid.Row="0">
<Button Content="Customers"
Command="{Binding NavCommand}"
CommandParameter="customers"
Grid.Column="0"
Grid.Row="0"/>
<Button Content="Orders"
Command="{Binding NavCommand}"
CommandParameter="orders"
Grid.Column="2"
Grid.Row="0"/>
</Grid>
<Grid x:Name="MainContent"
Grid.Row="1">
<ContentControl Content="{Binding CurrentViewModel}"/>
</Grid>
</Grid>
</Page>
This is my code-behind MainPageView.xaml.cs - here is where the StackoverflowException is thrown in the constructor it keeps calling it.
using Windows.UI.Xaml.Controls;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace MVVMHierarchiesDemo
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPageView : Page
{
public MainPageView()
{
this.InitializeComponent();
}
}
}
This is my BindableBase.cs as the tutorial shows:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace MVVMHierarchiesDemo
{
/*The main idea behind this class is to encapsulate the INotifyPropertyChanged implementation
* and provide helper methods to the derived class so that they can easily trigger the appropriate notifications.
* Following is the implementation of BindableBase class.*/
public class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName]string propertyName = null)
{
if (object.Equals(member, val))
return;
member = val;
OnPropertyChanged(propertyName);
}
}
}
This is MyCommand.cs or better known as the relay command pattern:
using System;
using System.Windows.Input;
namespace MVVMHierarchiesDemo
{
/* Now it's time to actually start doing some view switching using our CurrentViewModel property.
* We just need some way to drive the setting of this property. And we're going to make it so that
* the end user can command going to the customer list or to the order view. First add a new class
* in your project which will implement the ICommand interface. Following is the implementation of
* ICommand interface.*/
public class MyCommand<T> : ICommand
{
Action<T> _TargetExecuteMethod;
Func<T, bool> _TargetCanExecuteMethod;
public MyCommand(Action<T> targetExecuteMethod)
{
_TargetExecuteMethod = targetExecuteMethod;
}
public MyCommand(Action<T> targetExecuteMethod, Func<T,bool> targetCanExecuteMethod)
{
_TargetExecuteMethod = targetExecuteMethod;
_TargetCanExecuteMethod = targetCanExecuteMethod;
}
public event EventHandler CanExecuteChanged = delegate { };
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
bool ICommand.CanExecute(object parameter)
{
if (_TargetCanExecuteMethod != null)
{
T tparam = (T)parameter;
return _TargetCanExecuteMethod(tparam);
}
if (_TargetExecuteMethod != null)
return true;
return false;
}
void ICommand.Execute(object parameter)
{
if(_TargetExecuteMethod!=null)
{
T tparam = (T)parameter;
_TargetExecuteMethod(tparam);
}
}
}
}
This is my usercontrol for OrdersView.xaml:
<UserControl
x:Class="MVVMHierarchiesDemo.Views.OrderView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVMHierarchiesDemo.Views"
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>
<TextBlock Text="Order View"/>
</Grid>
</UserControl>
This is my user control CustomerListView.xaml:
<UserControl
x:Class="MVVMHierarchiesDemo.Views.CustomerListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVMHierarchiesDemo.Views"
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>
<TextBlock Text="Customer List View"/>
</Grid>
</UserControl>
This is my OrderViewModel:
namespace MVVMHierarchiesDemo.ViewModel
{
/*Derive all of your ViewModels from BindableBase class.*/
public class OrderViewModel : BindableBase
{
}
}
This is my CustomerViewModel:
namespace MVVMHierarchiesDemo.ViewModel
{
/*Derive all of your ViewModels from BindableBase class.*/
public class CustomerListViewModel : BindableBase
{
}
}
Finally this is my MainPageViewModel:
namespace MVVMHierarchiesDemo.ViewModel
{
/*Derive all of your ViewModels from BindableBase class.*/
public class MainPageViewModel : BindableBase
{
public MainPageViewModel()
{
NavCommand = new MyCommand<string>(OnNavigation);
}
private CustomerListViewModel _customerListViewModel = new CustomerListViewModel();
private OrderViewModel _orderViewModel = new OrderViewModel();
private BindableBase _currentViewModel;
public BindableBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
SetProperty(ref _currentViewModel, value);
}
}
public MyCommand<string> NavCommand { get; private set; }
private void OnNavigation(string destination)
{
switch (destination)
{
case "orders":
{
CurrentViewModel = _orderViewModel;
break;
}
case "customers":
default:
CurrentViewModel = _customerListViewModel;
break;
}
}
}
}
and lastly I think the MainPageView is the one causing the infinite looping but I don't understand why?
If somebody could be so kind to tell me what I am doing wrong on UWP?
Also I could use MVVM Light or MVVMCross I'm not interested on those solutions I want to learn MVVM by hand and later on i might check those frameworks.
It's because in your MainPageView.xaml you have this:
<Page.DataContext>
<local:MainPageView/>
</Page.DataContext>
So every MainPageview creates a nested MainPageView as its DataContext. These are created until you blow the stack.
I think you meant to put a MainPageViewModel in here.

Why are my User Settings saved only the first time this method is called?

I have a WPF MVVM app, which gets its data from a user setting which is an ObservableCollection of type Copyable (a custom class) called Copyables. Within the main view model (ClipboardAssistantViewModel), I set the source of a CollectionViewSource to Copyables. This is then bound to an ItemsControl in the main view (MainWindow). The DataTemplate for this ItemsControl is a user control, 'CopyableControl', which is essentially a button, but with attached properties that allow me to bind data and commands to it.
When a user clicks on a CopyableControl, a view model (DefineCopyableViewModel) is added to an ObservableCollection of those in ClipboardAssistantViewModel, and that collection is bound to an ItemsControl in MainWindow. The DataTemplate of this is a UserControl called DefineCopyableControl, which is set up in such a way that the current values associated with the clicked Copyable are bound to textboxes in the DefineCopyableControl for editing.
My problem: There is a method in DefineCopyableViewModel, EditCopyable(), which only works on the first run (its job is to save the user settings once any edits have taken place and the user clicks "OK"). If I click the CopyableControl and make an edit, then click "OK", then click it again, make another edit, then click "OK", then close the application and open it again, only the first edit has been saved (even though the UI was updated with the edited value both times). It seems to have something to do with the data-binding need to be "refreshed"; please see the comments in this method in the code for my findings around this.
My code is as follows:
Model:
namespace ClipboardAssistant.Models
{
public class Copyable : INotifyPropertyChanged
{
// INotifyPropertyChanged implementation
private string name;
public string Name
{
get { return name; }
set
{
if (value != name)
{
name = value;
NotifyPropertyChanged("Name");
}
}
}
private string textToCopy;
public string TextToCopy
{
get { return textToCopy; }
set
{
if (value != textToCopy)
{
textToCopy = value;
NotifyPropertyChanged("TextToCopy");
}
}
}
public Copyable() { }
public Copyable(string Name, string TextToCopy)
{
this.Name = Name;
this.TextToCopy = TextToCopy;
}
}
}
ViewModels:
namespace ClipboardAssistant.ViewModels
{
public class ClipboardAssistantViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged Implementation
public CollectionViewSource CopyablesView { get; set; }
public ObservableCollection<DefineCopyableViewModel> Definers { get; set; }
public CopyableClickCommand CopyableClickCommand { get; set; }
public ClipboardAssistantViewModel()
{
Definers = new ObservableCollection<DefineCopyableViewModel>();
CopyablesView = new CollectionViewSource();
CopyablesView.Source = Properties.Settings.Default.Copyables;
CopyableClickCommand = new CopyableClickCommand(this);
EditModeClickCommand = new EditModeClickCommand(this);
}
public void RefreshCopyables()
{
// Both these methods of refreshing appear to have the same effect.
Properties.Settings.Default.Copyables = (ObservableCollection<Copyable>)CopyablesView.Source;
CopyablesView.Source = Properties.Settings.Default.Copyables;
}
public void EditCopyable(Copyable Copyable)
{
Definers.Add(new DefineCopyableViewModel(Copyable, this));
}
}
}
namespace ClipboardAssistant.ViewModels
{
public class DefineCopyableViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged Implementation
public ClipboardAssistantViewModel MyParent { get; set; }
public Copyable Copyable { get; set; }
public DefinerOKClickCommand DefinerOKClickCommand { get; set; }
public DefineCopyableViewModel(Copyable Copyable, ClipboardAssistantViewModel MyParent)
{
this.Copyable = Copyable;
this.MyParent = MyParent;
DefinerOKClickCommand = new DefinerOKClickCommand(this);
}
public void EditCopyable()
{
// Refresh, save, no refresh, save -> doesn't save second edit.
// Save, refresh, save, no refresh -> does save second edit.
MessageBoxResult r = MessageBox.Show("Refresh?", "Refresh", MessageBoxButton.YesNo);
if (r == MessageBoxResult.Yes)
{
MyParent.RefreshCopyables();
}
// These two MessageBox methods (save and refresh) can be swapped around (see above comments).
MessageBoxResult s = MessageBox.Show("Save?", "Save", MessageBoxButton.YesNo);
if (s == MessageBoxResult.Yes)
{
Properties.Settings.Default.Save();
}
MyParent.Definers.Remove(this);
}
}
}
MainWindow:
<Window x:Class="ClipboardAssistant.Views.MainWindow" x:Name="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:vm="clr-namespace:ClipboardAssistant.ViewModels"
xmlns:ctrls="clr-namespace:ClipboardAssistant.Controls"
mc:Ignorable="d"
Title="Clipboard Assistant" Height="400" Width="700">
<Window.DataContext>
<vm:ClipboardAssistantViewModel />
</Window.DataContext>
<Grid>
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="20" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding CopyablesView.View}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ctrls:CopyableControl Copyable="{Binding}"
ClickCopyable="{Binding DataContext.CopyableClickCommand, ElementName=mainWindow}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<DockPanel Grid.Row="2">
<Button x:Name="btnEditCopyableMode" HorizontalAlignment="Left" DockPanel.Dock="Left"
Content="Edit" Margin="0,0,10,0" Command="{Binding EditModeClickCommand}" />
</DockPanel>
</Grid>
<ItemsControl ItemsSource="{Binding Definers}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ctrls:DefineCopyableControl Copyable="{Binding DataContext.Copyable}"
ClickCancel="{Binding DataContext.DefinerCancelClickCommand, ElementName=mainWindow}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
CopyableControl:
<UserControl x:Class="ClipboardAssistant.Controls.CopyableControl" x:Name="copyableControl"
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:local="clr-namespace:ClipboardAssistant.Controls"
mc:Ignorable="d" d:DesignHeight="75" d:DesignWidth="200">
<Grid Width="200" Height="75">
<Button Command="{Binding ClickCopyable, ElementName=copyableControl}"
CommandParameter="{Binding Copyable, ElementName=copyableControl}"
Content="{Binding Copyable.Name, ElementName=copyableControl}"
Style="{StaticResource CopyableMainButtonStyle}" />
</Grid>
</UserControl>
namespace ClipboardAssistant.Controls
{
public partial class CopyableControl : UserControl
{
public static readonly DependencyProperty ClickCopyableProperty =
DependencyProperty.Register("ClickCopyable", typeof(ICommand), typeof(CopyableControl));
public ICommand ClickCopyable
{
get { return (ICommand)GetValue(ClickCopyableProperty); }
set { SetValue(ClickCopyableProperty, value); }
}
public static readonly DependencyProperty CopyableProperty =
DependencyProperty.Register("Copyable", typeof(Copyable), typeof(CopyableControl));
public Copyable Copyable
{
get { return (Copyable)GetValue(CopyableProperty); }
set { SetValue(CopyableProperty, value); }
}
public CopyableControl()
{
InitializeComponent();
}
}
}
DefineCopyableControl:
<UserControl x:Class="ClipboardAssistant.Controls.DefineCopyableControl" x:Name="defineCopyableControl"
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="300" d:DesignWidth="500">
<Grid x:Name="MainGrid" Background="Blue">
<Grid Width="200" Height="180">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="10" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="20" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="Name" Foreground="White" />
<TextBox Grid.Row="1" Text="{Binding Copyable.Name}" x:Name="tbN" />
<Label Grid.Row="3" Content="Copyable Text" Foreground="White" />
<TextBox Grid.Row="4" Text="{Binding Copyable.TextToCopy}" x:Name="tbTTC" />
<DockPanel Grid.Row="6">
<Button Width="70" Content="OK" DockPanel.Dock="Right" HorizontalAlignment="Right"
Command="{Binding DefinerOKClickCommand}"
CommandParameter="{Binding ElementName=defineCopyableControl}" />
</DockPanel>
</Grid>
</Grid>
</UserControl>
public partial class DefineCopyableControl : UserControl
{
public static readonly DependencyProperty CopyableProperty =
DependencyProperty.Register("Copyable", typeof(Copyable), typeof(DefineCopyableControl));
public Copyable Copyable
{
get { return (Copyable)GetValue(CopyableProperty); }
set { SetValue(CopyableProperty, value); }
}
public DefineCopyableControl()
{
InitializeComponent();
}
}
Commands:
namespace ClipboardAssistant.ViewModels.Commands
{
public class CopyableClickCommand : ICommand
{
public ClipboardAssistantViewModel ViewModel { get; set; }
public CopyableClickCommand(ClipboardAssistantViewModel viewModel)
{
ViewModel = viewModel;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
Copyable cp = (Copyable)parameter;
ViewModel.EditCopyable(cp);
}
}
}
namespace ClipboardAssistant.ViewModels.Commands
{
public class DefinerOKClickCommand : ICommand
{
public DefineCopyableViewModel ViewModel { get; set; }
public DefinerOKClickCommand(DefineCopyableViewModel viewModel)
{
ViewModel = viewModel;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
ViewModel.EditCopyable();
}
}
}
Settings:
namespace ClipboardAssistant.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::System.Collections.ObjectModel.ObservableCollection<ClipboardAssistant.Models.Copyable> Copyables {
get {
return ((global::System.Collections.ObjectModel.ObservableCollection<ClipboardAssistant.Models.Copyable>)(this["Copyables"]));
}
set {
this["Copyables"] = value;
}
}
}
}
I'm assuming you are using Visual Studio. In that case, in the My Project do you have the settings listed in the settings tab?
I ran into the same issue a while back where I tried to programatically create/save/update settings and was unsucessful until I created the setting in the Settings tab. Once that was complete I was able to make my saves as necessary.
The you just use
MySettings.Default.SettingName = value
MySettings.Default.Save()
Hope this helps!

How to show a view using Catel

I am developing a simple app with Catel. I have previously used ReactiveUI and am having a little trouble getting started with Catel. I have a basic MainWindow. In there I have a toolbar with some buttons. When a button is selected I would like to show in the bottom pane of the application a user control (based on what they selected). So far I have one basic view that has a listview in it and then a view model behind it. I need help in figuring out how to show that view when the button is selected. Thank you for your help. Here is what I have so far. As you can see, when the 'ExecutePlayersButtonCommand' in the mainviewmodel is run, i want it to show the players view. I am not sure how to get this. I can get it to come up in a popup but that is not what i want. In reactiveui I can do it with the Router.Navigate function. Not sure how to do it here.
<catel:DataWindow xmlns:Controls="clr-namespace:FootballSim.Controls;assembly=FootballSim.Controls"
xmlns:RedfoxSoftwareCustomControls="clr-namespace:RedfoxSoftwareCustomControls;assembly=RedfoxSoftwareCustomControls"
x:Class="FootballSim.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com"
xmlns:views="clr-namespace:FootballSim.Views"
Style="{StaticResource {x:Type Window}}"
ShowInTaskbar="True" ResizeMode="CanResize" SizeToContent="Manual"
WindowStartupLocation="Manual" WindowState="Maximized" Icon="/FootballSim;component/redfox_software_48x48.ico">
<!-- Resources -->
<catel:DataWindow.Resources>
</catel:DataWindow.Resources>
<!-- Content -->
<catel:StackGrid x:Name="LayoutRoot">
<catel:StackGrid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</catel:StackGrid.RowDefinitions>
<DockPanel>
<StackPanel DockPanel.Dock="Top">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="50" />
<RowDefinition Height="100*" />
</Grid.RowDefinitions>
<RedfoxSoftwareCustomControls:WpfCustomApplicationMenuBar x:Name="CustomMenuBar" Grid.Row="0" />
<StackPanel Grid.Row="1">
<Button HorizontalAlignment="Left" Command="{Binding PlayersButtonCommand}" Background="Transparent">
<StackPanel>
<Image Source="/FootballSim;component/Resources/People_white_48.png" Width="30"></Image>
<TextBlock Text="Players" Foreground="White" HorizontalAlignment="Center"/>
</StackPanel>
</Button>
</StackPanel>
<DockPanel LastChildFill="True" Grid.Row="2">
<ContentControl Content="{Binding contentObject}" />
<!--<views:PlayersView DataContext="{Binding PlayerProviders}" />-->
</DockPanel>
</Grid>
</StackPanel>
</DockPanel>
</catel:StackGrid>
</catel:DataWindow>
using Catel.Windows;
using FootballSim.Scripts;
using FootballSim.Services;
using FootballSim.ViewModels;
using System;
using System.ComponentModel;
using Catel.MVVM.Views;
using Catel.Windows.Data;
using Catel.MVVM;
namespace FootballSim.Views
{
public partial class MainWindow : DataWindow
{
private RedfoxMessage logger = new RedfoxMessage();
private PopulateDatabase database = new PopulateDatabase();
public MainWindow() : base(DataWindowMode.Custom)
{
try
{
InitializeComponent();
logger.LogMessage("Starting application.");
CustomMenuBar.AboutButtonClickEvent += CustomMenuBar_AboutButtonClickEvent;
}
catch (Exception e)
{
RedfoxMessage.LogMessage(e, NLog.LogLevel.Error);
}
}
private void CustomMenuBar_AboutButtonClickEvent(object sender, System.EventArgs args)
{
(DataContext as IMainWindowViewModel).AboutButtonCommand.Execute(null);
}
}
}
using Catel.Data;
using Catel.IoC;
using Catel.MVVM;
using Catel.MVVM.Services;
using FootballSim.Models;
using FootballSim.Scripts;
using FootballSim.Views;
using System.Collections.Generic;
using System.Windows;
namespace FootballSim.ViewModels
{
public interface IMainWindowViewModel
{
Command PlayersButtonCommand { get; }
Command AboutButtonCommand { get; }
List<Player> PlayerProviders { get; }
Player SelectedPlayerProvider { get; }
object ContentObject { get; }
}
/// <summary>
/// MainWindow view model.
/// </summary>
public class MainWindowViewModel : ViewModelBase, IMainWindowViewModel
{
//private readonly IUIVisualizerService _uiVisualizerService;
private PopulateDatabase _populateDatabase;
public static readonly PropertyData PlayerProvidersProperty = RegisterProperty("PlayerProviders", typeof(List<Player>));
public static readonly PropertyData SelectedPlayerProviderProperty = RegisterProperty("SelectedPlayerProvider", typeof(Player));
public Command PlayersButtonCommand { get; private set; }
public Command AboutButtonCommand { get; private set; }
public object ContentObject { get; set; }
public MainWindowViewModel() : base()
{
//var dependencyResolver = this.GetDependencyResolver();
//_uiVisualizerService = dependencyResolver.Resolve<IUIVisualizerService>();
_populateDatabase = new PopulateDatabase();
PlayerProviders = _populateDatabase.Players;
var pv = new PlayersView();
pv.DataContext = PlayerProviders;
ContentObject = pv;
PlayersButtonCommand = new Command(ExecutePlayersButtonCommand);
AboutButtonCommand = new Command(ExecuteAboutButtonCommand);
}
private void ExecutePlayersButtonCommand()
{
PlayerProviders = _populateDatabase.Players;
MessageBox.Show("Players");
}
private void ExecuteAboutButtonCommand()
{
var aboutView = new AboutView();
aboutView.ShowDialog();
}
public List<Player> PlayerProviders
{
get
{
return GetValue<List<Player>>(PlayerProvidersProperty);
}
set
{
SetValue(PlayerProvidersProperty, value);
}
}
public Player SelectedPlayerProvider
{
get
{
return GetValue<Player>(SelectedPlayerProviderProperty);
}
set
{
SetValue(SelectedPlayerProviderProperty, value);
}
}
//protected override void Initialize()
//{
// SelectedPlayerProvider = PlayerProviders[0];
//}
public override string Title { get { return "FootballSim"; } }
}
}
<catel:UserControl x:Class="FootballSim.Views.PlayersView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com"
xmlns:views="clr-namespace:FootballSim.Views"
xmlns:viewmodels="clr-namespace:FootballSim.ViewModels"
xmlns:models="clr-namespace:FootballSim.Models;assembly=FootballSim.Core">
<!-- Resources -->
<UserControl.Resources>
</UserControl.Resources>
<!-- Content -->
<catel:StackGrid>
<catel:StackGrid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</catel:StackGrid.RowDefinitions>
<Label Content="{Binding Title}" Foreground="White" Grid.Row="0" />
<Label Content="Here goes your real content" Foreground="White" Grid.Row="1"/>
<ListBox Grid.Row="2" ItemsSource="{Binding PlayersCollection}" SelectedItem="{Binding SelectedPlayer}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding ColumnValue}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!--<views:PlayersView Grid.Column="1" DataContext="{Binding SelectedPlayer}" />-->
</catel:StackGrid>
</catel:UserControl>
namespace FootballSim.Views
{
using Catel.Windows.Controls;
using FootballSim.ViewModels;
public partial class PlayersView : UserControl
{
public PlayersView()
{
InitializeComponent();
}
}
}
namespace FootballSim.ViewModels
{
using Catel.MVVM;
using FootballSim.Models;
using System.Collections.Generic;
/// <summary>
/// UserControl view model.
/// </summary>
public class PlayersViewModel : ViewModelBase, IPlayersViewModel
{
public List<Player> Players { get; protected set; }
public PlayersViewModel(List<Player> players) : base()
{
Players = players;
}
public override string Title { get { return "View model title"; } }
// TODO: Register models with the vmpropmodel codesnippet
// TODO: Register view model properties with the vmprop or vmpropviewmodeltomodel codesnippets
// TODO: Register commands with the vmcommand or vmcommandwithcanexecute codesnippets
}
}
There are several ways of navigating in Catel:
IUIVisualizerService => show other dialogs
INavigationService => navigate to pages / close application / etc
Maybe it's a good idea for you to read the getting started guide of Catel.

Windows Phone 8.1 - Menu flyout item click command c#

I am writing an application for Windows Phone 8.1, and I wanted to use a flyout on listView item. Because I am doing my best to write nice app, I am trying to use MVVM pattern and resource dictionaries with templates insead of all xaml in one page.
However, I can't bind my MenuFlyoutItem Command - it seems like it doesn't see the datacontext of the page, or it has some other dataContext. Here is some code:
1) My template in a separate resource dictionary:
<Grid Margin="0, 0, 0, 10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="4*" />
</Grid.ColumnDefinitions>
<Grid.Resources>
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<converters:EmptyDateConverter x:Key="EmptyDateConverter" />
</Grid.Resources>
<i:Interaction.Behaviors>
<icore:EventTriggerBehavior EventName="Holding">
<converters:OpenMenuFlyoutAction />
</icore:EventTriggerBehavior>
</i:Interaction.Behaviors>
<FlyoutBase.AttachedFlyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="AddToCalendarMenuItem" Command="{Binding AddToCalendar}" />
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
<Image Grid.Column="0" Source="{Binding ThumbnailUri}"/>
<StackPanel Grid.Column="1" Orientation="Vertical" Margin="10,0,0,0">
<TextBlock Text="{Binding Title}" Style="{StaticResource ListItemTitle}"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
<TextBlock x:Uid="DvdReleaseDate" />
<TextBlock Text="{Binding DvdRelease, Converter={StaticResource EmptyDateConverter}}" />
</StackPanel>
</StackPanel>
</Grid>
2) And here is the list view:
<ListView Grid.Row="1" x:Name="SearchListView"
ItemsSource="{Binding SearchList}"
ItemTemplate="{StaticResource SearchListTemplate}" SelectionChanged="NavigateToMovieDetails" />
My ViewModel is a static kind of singleton in the app.xaml.cs
I've tried to create a new instance of the VM in xaml, but it didn't work - maybe I was doing smth wrong.
I would really appreciate Your help! Thanks in advance.
Best regards,
Roman
If you stick with that <ItemTemplate> you will have to have a Command per every Model in your ViewModel, which is not ideal.
To set a single Command and have a CommandParameter see this SO Tutorial I made it is too much code to type here:
Implement a ViewModel Single Command with CommandParamater
#Chubosaurus Software following your approach I came up with this.
Here is a ListView bound to a list of todo items placed inside a DataTemplate which contains a TextBlock having a MenuFlyout to show edit, delete context menu kind of thing.
The key to bind the commands in the view model to the MenuFlyoutItem is to give the ListView a name and do element binding using the ElementName property in the Command to point to the ListView's name. To access the commands in our view model we've to go through the ListView's DataContext and bind it to a command on it because the DataContext of the MenuFlyoutItem is an item in the ItemsSource
The MainPage.xaml
<Page
x:Class="UWA.MenuFlyout.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UWA.MenuFlyout"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:UWA.MenuFlyout.ViewModels"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:common="using:UWA.MenuFlyout.Core"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Margin="24,24">
<ListView x:Name="Todos" ItemsSource="{Binding Todos}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Action}">
<FlyoutBase.AttachedFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="edit"
Command="{Binding ElementName=Todos, Path=DataContext.EditTodo}"
CommandParameter="{Binding}"/>
<MenuFlyoutItem Text="delete"
Command="{Binding ElementName=Todos, Path=DataContext.DeleteTodo}"
CommandParameter="{Binding}"/>
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Holding">
<common:OpenMenuFlyoutAction/>
</core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="RightTapped">
<common:OpenMenuFlyoutAction/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
The MainPage.xaml.cs is where the DataContext of the MainPage is set.
namespace UWA.MenuFlyout
{
using UWA.MenuFlyout.ViewModels;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
this.DataContext = new MainViewModel();
}
/// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached.
/// This parameter is typically used to configure the page.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// TODO: Prepare page for display here.
// TODO: If your application contains multiple pages, ensure that you are
// handling the hardware Back button by registering for the
// Windows.Phone.UI.Input.HardwareButtons.BackPressed event.
// If you are using the NavigationHelper provided by some templates,
// this event is handled for you.
}
}
}
The MainViewModel.cs containing the Todos which is an ObservableCollection type and the EditTodo and DeleteTodo commands.
namespace UWA.MenuFlyout.ViewModels
{
using System.Collections.ObjectModel;
using System.Windows.Input;
using UWA.MenuFlyout.Core;
using UWA.MenuFlyout.Models;
public class MainViewModel : BaseViewModel
{
private ICommand editTodo;
private ICommand deleteTodo;
public MainViewModel()
{
this.Todos = new ObservableCollection<TodoModel>
{
new TodoModel { Id = 1, Action = "Buy Milk", IsDone = true },
new TodoModel { Id = 2, Action = "Buy Groceries", IsDone = false }
};
}
public ObservableCollection<TodoModel> Todos { get; set; }
public ICommand EditTodo
{
get
{
if (this.editTodo == null)
{
this.editTodo = new RelayCommand(this.OnEditTodo);
}
return this.editTodo;
}
}
public ICommand DeleteTodo
{
get
{
if (this.deleteTodo == null)
{
this.deleteTodo = new RelayCommand(this.OnDeleteTodo);
}
return this.deleteTodo;
}
}
public void OnEditTodo(object parameter)
{
// perform edit here
var todo = parameter as TodoModel;
}
public void OnDeleteTodo(object parameter)
{
// perform delete here
var todo = parameter as TodoModel;
}
}
}
The Model
namespace UWA.MenuFlyout.Models
{
public class TodoModel
{
public int Id { get; set; }
public string Action { get; set; }
public bool IsDone { get; set; }
}
}
The BaseViewModel which implements the INotifyPropertyChanged.
namespace UWA.MenuFlyout.Core
{
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
A simple ICommand implementation.
namespace UWA.MenuFlyout.Core
{
using System;
using System.Windows.Input;
public class RelayCommand : ICommand
{
private Action<object> action;
public RelayCommand(Action<object> action)
{
this.action = action;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this.action(parameter);
}
}
}
The OpenMenuFlyoutAction which implements DependencyObject and IAction to open the MenuFlyout by using the Execute method on the IAction interface.
namespace UWA.MenuFlyout.Core
{
using Microsoft.Xaml.Interactivity;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls.Primitives;
public class OpenMenuFlyoutAction : DependencyObject, IAction
{
public object Execute(object sender, object parameter)
{
var frameworkElement = sender as FrameworkElement;
var flyoutBase = FlyoutBase.GetAttachedFlyout(frameworkElement);
flyoutBase.ShowAt(frameworkElement);
return null;
}
}
}

WPF Binding ListBox Master/Detail

I can get this working with an XmlDataSource but not with my own classes. All I want to do is bind the listbox to my collection instance and then link the textbox to the listbox so I can edit the person's name (two-way). I've deliberately kept this as simple as possible in the hope that somebody can fill in the blanks.
XAML:
<Window x:Class="WpfListTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfListTest"
Title="Window1" Height="300" Width="600">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="0">
<ListBox />
</DockPanel>
<DockPanel Grid.Column="2">
<StackPanel>
<Label>Name</Label>
<TextBox />
</StackPanel>
</DockPanel>
</Grid>
</Window>
C# code behind:
namespace WpfListTest
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public People MyPeeps = new People();
public Window1()
{
InitializeComponent();
MyPeeps.Add(new Person("Fred"));
MyPeeps.Add(new Person("Jack"));
MyPeeps.Add(new Person("Jill"));
}
}
public class Person
{
public string Name { get; set; }
public Person(string newName)
{
Name = newName;
}
}
public class People : List<Person>
{
}
}
All the examples on the web seem to have what is effectively a static class returning code-defined data (like return new Person("blah blah")) rather than my own instance of a collection - in this case MyPeeps. Or maybe I'm not uttering the right search incantation.
One day I might make a sudden breakthrough of understanding this binding stuff but at the moment it's baffling me. Any help appreciated.
The correct way would be to use the MVVM pattern and create a ViewModel like so:
public class MainWindowViewModel : INotifyPropertyChanged
{
private People _myPeeps;
private Person _selectedPerson;
public event PropertyChangedEventHandler PropertyChanged;
public People MyPeeps
{
get { return _myPeeps; }
set
{
if (_myPeeps == value)
{
return;
}
_myPeeps = value;
RaisePropertyChanged("MyPeeps");
}
}
public Person SelectedPerson
{
get { return _selectedPerson; }
set
{
if (_selectedPerson == value)
{
return;
}
_selectedPerson = value;
RaisePropertyChanged("SelectedPerson");
}
}
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Initialize it in your View's code behind like so:
public partial class MainWindow : Window
{
private readonly MainWindowViewModel _viewModel;
public MainWindow()
{
_viewModel = new MainWindowViewModel();
_viewModel.MyPeeps = new People();
_viewModel.MyPeeps.Add(new Person("Fred"));
_viewModel.MyPeeps.Add(new Person("Jack"));
_viewModel.MyPeeps.Add(new Person("Jill"));
DataContext = _viewModel;
InitializeComponent();
}
}
And bind the data like so:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160" />
<ColumnDefinition Width="3" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="0">
<ListBox SelectedItem="{Binding SelectedPerson}"
DisplayMemberPath="Name"
ItemsSource="{Binding MyPeeps}" />
</DockPanel>
<DockPanel Grid.Column="2">
<StackPanel>
<Label>Name</Label>
<TextBox Text="{Binding SelectedPerson.Name}" />
</StackPanel>
</DockPanel>
</Grid>
</Window>
The binding will work like this:
The DataContext of the window itself is set to the ViewModel instance. Because the ListBox and the TextBox don't specify any DataContext, they inherit it from the Window. The bindings on an object always work relative to the DataContext if nothing else is being specified. That means that the TextBox binding looks for a property SelectedPerson in its DataContext (i.e., in the MainWindowViewModel) and for a Property Name in that SelectedPerson.
The basic mechanics of this sample are as follows:
The SelectedPerson property on the ViewModel is always synchronized with the SelectedItem of the ListBox and the Text property of the TextBox is always synchronized with the Name property of the SelectedPerson.
Try to inherit your People class from ObservableCollection<Person>

Categories