I want to outsource two images to a custom usercontrol, which should have two properties to be set, one for the source of each image.
But I ran into trouble with the datacontext, which isnt recognised correctly. It might also be a problem, that its the first time that I use dependency properties. Anyway, I hope you can figure out my thoughts and help me here, here comes the sourcecode:
MainViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
private string _spielerL1;
private string _spielerL2;
public MainWindowViewModel()
{
SpielerL1 = System.IO.Directory.GetCurrentDirectory() + #"\Images\queen_of_clubs.png";
SpielerL2 = System.IO.Directory.GetCurrentDirectory() + #"\Images\queen_of_diamonds.png";
[...]
}
public string SpielerL1
{
get { return _spielerL1; }
private set
{
_spielerL1 = value;
OnPropertyChanged("SpielerL1");
}
}
public string SpielerL2
{
get { return _spielerL2; }
private set
{
_spielerL2 = value;
OnPropertyChanged("SpielerL2");
}
}
}
In my mainwindow view I am only instantiating the viewmodel and using the control with SourceLeft="{Binding SpielerL1}" and SourceRight="{Binding SpielerL2}"...
My control code behind looks like this (deleted sourceright to make it shorter):
public partial class HandControl
{
public HandControl()
{
InitializeComponent();
DataContext = this;
}
public string SourceLeft
{
get
{
return (string) GetValue(SourceLeftProperty);
}
set
{
SetValue(SourceLeftProperty, value);
}
}
public static readonly DependencyProperty SourceLeftProperty = DependencyProperty.Register("SourceLeft", typeof(string), typeof(HandControl), new PropertyMetadata(""));
}
And finally my usercontrol xaml, which isnt recognising the datacontext or atleast not showing my images:
<UserControl x:Class="FoolMe.Gui.Controls.HandControl"
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="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<Image Grid.Column="1"
Source="{Binding SourceLeft}" />
<Image Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
Source="{Binding SourceRight}" />
</Grid>
</UserControl>
Since I havent done much with WPF and usercontrols yet, I have no clue, whats wrong. Without the usercontrol it has working fine, but outsourcing it like this, my window keeps "white".
Anyone got an idea, what went wrong?
You shouldn't set the DataContext of the UserControl to itself. However, your real problem comes from your Binding on the Image elements. You should use a RelativeSource Binding instead:
<Image Grid.Column="1" Source="{Binding SourceLeft, RelativeSource={RelativeSource
AncestorType={x:Type YourXmlNamespacePrefix:HandControl}}}" />
<Image Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Source="{Binding SourceRight,
RelativeSource={RelativeSource AncestorType={x:Type YourXmlNamespacePrefix:
HandControl}}}" />
Related
I have a MainWindowViewModel and my MainWindow contains a frame to display project pages.
The first page being displayed is a list of recently opened projects(Similar to Microsoft word) which has it's own ViewModel.
There is no problem in loading the list but when I want to send the user-selected item from this list to the MainWindowViewModel I can not use Find-Ancestor to reach the Window DataContext(It looks like the frame has some restrictions).
How can I send the user-selected item to the MainWindowViewModel?
public class RecentlyOpenedFilesViewModel
{
readonly IFileHistoryService _fileHistoryService;
private ObservableCollection<RecentlyOpenedFileInfo> _RecentlyOpenedFilesList;
public ObservableCollection<RecentlyOpenedFileInfo> RecentlyOpenedFilesList
{
get { return _RecentlyOpenedFilesList; }
set { _RecentlyOpenedFilesList = value; RaisePropertyChanged(); }
}
public RecentlyOpenedFilesViewModel( IFileHistoryService fileService):base()
{
_fileHistoryService = fileService;
RecentlyOpenedFilesList=new ObservableCollection<RecentlyOpenedFileInfo>(_fileHistoryService.GetFileHistory());
}
public void RefreshList()
{
RecentlyOpenedFilesList = new ObservableCollection<RecentlyOpenedFileInfo>(_fileHistoryService.GetFileHistory());
}
}
<Page
x:Class="MyProject.Views.V3.Other.RecentlyOpenedFilesPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject.Views.V3.Other"
xmlns:vmv3="clr-namespace:MyProject"
Title="RecentlyOpenedFilesPage">
<Page.Resources>
<DataTemplate x:Key="RecentlyOpenedFileInfoTemplate"
>
<Button
Height="70"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=DataContext.OpenProjectFromPathCommand}"
CommandParameter="{Binding}">
<Button.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="70" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<StackPanel
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Top">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Path}" />
</StackPanel>
<TextBlock
Grid.Row="0"
Grid.Column="1"
Margin="50,0,0,0"
VerticalAlignment="Center"
Text="{Binding DateModified}" />
</Grid>
</Button.Content>
</Button>
</DataTemplate>
</Page.Resources>
<Grid>
<ListView
ItemTemplate="{StaticResource RecentlyOpenedFileInfoTemplate}"
ItemsSource="{Binding RecentlyOpenedFilesList}" />
</Grid>
public RecentlyOpenedFilesPage(MainWindowViewModel vm)
{
this.DataContext = vm;
InitializeComponent();
}
Now I have a direct link between MainWindowViewModel and RecentlyOpenedFilesViewModel but I would like to remove this dependency and use another way of connection like(routed commands which I have a problem with)
The MainWindow contains a frame in which the RecentlyOpenedFilesPage is set to its content.
<Window
x:Class="MyProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:fw="clr-namespace:SourceChord.FluentWPF;assembly=FluentWPF" >
<Frame Name="frameMain"/></Window>
public class MainWindowViewModel : RecentlyOpenedFilesViewModel, IMainWindowViewModel
{
private void LoadRecentlyOpenedProjects()
{
CurrentView = new RecentlyOpenedFilesPage(this);
}
}
So, here is my suggested solution. It uses the basic idea to propagate the DataContext from the outside into a frame content, as presented in page.DataContext not inherited from parent Frame?
For demonstration purpose, I provide an UI with a button to load the page, a textblock to display the selected result from the list within the page and (ofcourse) the frame that holds the page.
<Window x:Class="WpfApplication1.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"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Name="parentGrid">
<TextBlock VerticalAlignment="Top" HorizontalAlignment="Right" Margin="5" Text="{Binding SelectedFile}" Width="150" Background="Yellow"/>
<Button VerticalAlignment="Top" HorizontalAlignment="Left" Margin="5" Click="Button_Click" Width="150">Recent Files List</Button>
<Frame Name="frameMain" Margin="5 50 5 5"
LoadCompleted="frame_LoadCompleted"
DataContextChanged="frame_DataContextChanged"/>
</Grid>
</Window>
Viewmodel classes:
public class BaseVm : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName]string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
public class MyWindowVm : BaseVm
{
private string _selectedFile;
public string SelectedFile
{
get => _selectedFile;
set
{
_selectedFile = value;
OnPropertyChanged();
}
}
}
public class MyPageVm : BaseVm
{
public ObservableCollection<MyRecentFile> Files { get; } = new ObservableCollection<MyRecentFile>();
}
public class MyRecentFile
{
public string Filename { get; set; }
public string FilePath { get; set; }
}
Main code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
parentGrid.DataContext = new MyWindowVm();
}
// Load Page on some event
private void Button_Click(object sender, RoutedEventArgs e)
{
frameMain.Content = new RecentlyOpenedFilesPage(new MyPageVm
{
Files =
{
new MyRecentFile { Filename = "Test1.txt", FilePath = "FullPath/Test1.txt"},
new MyRecentFile { Filename = "Test2.txt", FilePath = "FullPath/Test2.txt"}
}
});
}
// DataContext to Frame Content propagation
private void frame_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
UpdateFrameDataContext(sender as Frame);
}
private void frame_LoadCompleted(object sender, NavigationEventArgs e)
{
UpdateFrameDataContext(sender as Frame);
}
private void UpdateFrameDataContext(Frame frame)
{
var content = frame.Content as FrameworkElement;
if (content == null)
return;
content.DataContext = frame.DataContext;
}
}
Now, the page.xaml ... notice: we will set the page viewmodel to the pageRoot.DataContext, not to the page itself. Instead we expect the page datacontext to be handled from the outside (as we do in the MainWindow) and we can reference it with the page internal name _self:
<Page x:Class="WpfApplication1.RecentlyOpenedFilesPage"
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"
Title="RecentlyOpenedFilesPage"
Name="_self">
<Grid Name="pageRoot">
<ListView ItemsSource="{Binding Files}"
SelectedValue="{Binding DataContext.SelectedFile,ElementName=_self}"
SelectedValuePath="FilePath"
DisplayMemberPath="Filename"/>
</Grid>
</Page>
Page code behind to wire up the viewmodel:
public partial class RecentlyOpenedFilesPage : Page
{
public RecentlyOpenedFilesPage(MyPageVm myPageVm)
{
InitializeComponent();
pageRoot.DataContext = myPageVm;
}
}
As you can see, with this setup, no viewmodel knows about any involved view. The page doesn't handle the MainViewmodel, but the page requires a DataContext with a SelectedFile property to be provided from the outside.
The MainViewmodel doesn't know about the recent file list, but allows to set a selected file, no matter where it originates from.
The decision, to initialize the RecentlyOpenedFilesPage with a pre-created viewmodel is not important. You could just as well use internal logic to initialize the page with recent files, then the Mainwindow would not be involved.
Somewhat I've been trying to follow this MVVM tutorial using Hierarchies and navigation:
https://www.tutorialspoint.com/mvvm/mvvm_hierarchies_and_navigation.htm
I've done so far most of the tutorial, but when it comes to UWP it seems that Implicit binding is not available for UWP, so I can't replicate this tutorial, because even though I've used x:DataType with x:Key that the compiler asks for an x:key attribute in order to bind views with viewmodel, all I get is the fullname of my viewmodel instead of being able to see the actual content.
So can somebody help me how can I use hierarchies properly in UWP using Plain MVVM pattern without the help of tools such as MVVM Light or MVVM Cross.
I'll leave you the code I have so far for a UWP app:
<Page
x:Class="MVVMHeirarchiesDemo.MainPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVMHeirarchiesDemo"
xmlns:views="using:MVVMHeirarchiesDemo.Views"
xmlns:viewmodel="using:MVVMHeirarchiesDemo.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>
<viewmodel:MainPageViewModel/>
</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 x:Name="NavBar"
Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<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>
as you can see may main trouble are at my Page. Resources I guess with my binding at
because that line of code is not getting access to the actual content of my views.
this is my viewmodel for my main view:
namespace MVVMHeirarchiesDemo.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 finally this is my helper bindable class:
namespace MVVMHeirarchiesDemo
{
/*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);
}
}
}
I hope someone can help me out with this one.
well It took me a while but i was able to understand how to use DataTemplateSelector class, it turns out that this work around fit nicely to my problem since there is no implicit binding for UWP.
So if you are exploring UWP there isnt IMPLICIT BINDING you have to use explicit binding in order to make this specific problem work.
So this was my solution, I still use my BindableBase because it help me to choose the proper ViewModel for the View i'm calling, so there is no need to change it.
firstable I've created a Template class so I can retrieve the properties that will help me work with ExplicitBinding:
public class Template
{
public string DataType { get; set; }
public DataTemplate DataTemplate { get; set; }
}
Then I'll use a Collection but I'll use a Facade Pattern on this one, I kind love patterns are fun and elegant.
public class TemplateCollection : Collection<Template>
{
}
Then I've used a class by another answer that helps me to reuse it to solve my problem:
public class MyDataTemplateSelector : DataTemplateSelector
{
public TemplateCollection Templates { get; set; }
private ICollection<Template> _templateCache { get; set; }
public MyDataTemplateSelector()
{
}
private void InitTemplateCollection()
{
_templateCache = Templates.ToList();
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (_templateCache == null)
{
InitTemplateCollection();
}
if (item != null)
{
var dataType = item.GetType().ToString();
var match = _templateCache.Where(m => m.DataType == dataType).FirstOrDefault();
if (match != null)
{
return match.DataTemplate;
}
}
return base.SelectTemplateCore(item, container);
}
}
this class can be found here: How to associate view with viewmodel or multiple DataTemplates for ViewModel?
just I dont like to steal credits, but I did learned from this answer and my partner sugesstion MLavoie.
so this is my Fix view thanks to this I'm able to use navigation and also created a Hierarchical MVVM Pattern.
<Page
x:Class="MVVMHeirarchiesDemo.MainPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVMHeirarchiesDemo"
xmlns:dtempsltor="using:MVVMHeirarchiesDemo.Templates"
xmlns:views="using:MVVMHeirarchiesDemo.Views"
xmlns:viewmodel="using:MVVMHeirarchiesDemo.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>
<viewmodel:MainPageViewModel/>
</Page.DataContext>
<Page.Resources>
<dtempsltor:TemplateCollection2 x:Key="templates">
<dtempsltor:Template DataType="MVVMHeirarchiesDemo.ViewModel.CustomerListViewModel">
<dtempsltor:Template.DataTemplate>
<DataTemplate x:DataType="viewmodel:CustomerListViewModel">
<views:CustomerListView/>
</DataTemplate>
</dtempsltor:Template.DataTemplate>
</dtempsltor:Template>
<dtempsltor:Template DataType="MVVMHeirarchiesDemo.ViewModel.OrderViewModel">
<dtempsltor:Template.DataTemplate>
<DataTemplate x:DataType="viewmodel:OrderViewModel">
<views:OrderView/>
</DataTemplate>
</dtempsltor:Template.DataTemplate>
</dtempsltor:Template>
</dtempsltor:TemplateCollection2>
<dtempsltor:MyDataTemplateSelector x:Key="MyDataTemplateSelector"
Templates="{StaticResource templates}"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="NavBar"
Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<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 ContentTemplateSelector="{StaticResource MyDataTemplateSelector}"
Content="{Binding CurrentViewModel}"/>
</Grid>
</Grid>
thanks for those who helped me enlight myself it was great, to be able to solve it with some hints and also felt good to be able to solve it, I kinda love to code in C# and Xaml.
I want to create a Window which redeclares it's own DependencyProperty named Content.
public partial class InfoWindow : Window
{
public static new readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(object), typeof(InfoWindow), new PropertyMetadata(null));
public object Content
{
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
}
And XAML bind this property
<ContentControl Content="{Binding ElementName=_this, Path=Content}" />
It works fine, just the Visual Studio Designer complains Logical tree depth exceeded while traversing the tree. This could indicate a cycle in the tree.
Is there any way how to tell the Designer that binding is to the InfoWindow.Content and not Window.Content? Or is it a bad idea hide the property and should I renamed my property?
What I am trying to achieve here is the idea of dynamically defining the Buttons that are used to bring up different views for navigating to different forms. (See below: )
The link between the View and View Models are setup inside the Dictionary View_ViewModel which is used to identify the view to set for the Current view when the button is pressed.
(Note: I have tried to use the most basic objects avoiding IOC containers and such like, so as to make it easier to understand the code)
The most important thing to remember is to set the DataContext correctly otherwise you will get the Logical tree depth exceeded while traversing the tree. Error. You could either do this in the Code behind of the View or inside the XAML.
Example:
public partial class SetupForm : UserControl
{
public SetupForm()
{
InitializeComponent();
DataContext = new SetupFormVM();
}
}
OR
<UserControl.DataContext>
<SaleVM:SalesEntryVM />
</UserControl.DataContext>
Here is a code snippet which probably explains it more clearly and probably answers your question.
The view model defines the how many buttons and views you want in the Main Window. This is achieved by having ItemsControl binding to a list in View Model class.
The Button command is bounded to ICommand ChangeViewCommand property in the View Model class which evaluates the Button pressed and .calls ViewChange method which changes the CurrentView (this is bound to the Content in the XAML)
This is what my main Window xaml looks like.
<Window x:Class="MyNameSpace.Views.ApplicationWindow"
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:MyNameSpace.Views"
mc:Ignorable="d"
Title="ApplicationWindow" Height="Auto" Width="Auto">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition /> -------------------> repeated five times
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition /> -------------------------> repeated five times
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="5" >
<!-- Bind to List of Pages -->
<ItemsControl ItemsSource="{Binding ControlItemsNamesList}" DockPanel.Dock="Top" >
<!-- Stack the buttons horizontally --> The list contains the labels to assign to the buttons
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate> ---------------------------------------> This to stack the buttons Horizontally
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- This looks at the list items and creates a button with ControlName -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding ControlName}"
Command="{Binding DataContext.ChangeViewCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" ------> This is important for the Buttons to work Window or ContentControl.
CommandParameter="{Binding }"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
<ContentControl Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="5" Grid.RowSpan="4" Content="{Binding CurrentView}"/> ---------> This is where I want the new Windows to appear when I click the button
</Grid>
This is what one of my User control xaml looks like that will appear when I click the button in the Main Window.
<UserControl x:Class="MyNameSpace.Views.SetupForm"
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">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition /> ------------------- repeated five times
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/> ------------------- repeated five times
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" Grid.Column="0" Grid.RowSpan="5" Background="AliceBlue" Margin="0,0,0,0" >
<!-- Bind to List of Pages -->
<ItemsControl ItemsSource="{Binding ControlItemsNamesList}" DockPanel.Dock="Left" >
<!-- Stack the buttons horizontally -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- This looks at the list items and creates a button with ControlName -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding ControlName}"
Command="{Binding DataContext.ChangeViewCommand, RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}}"
CommandParameter="{Binding }"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
<ContentControl Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="5" Grid.RowSpan="4" Content="{Binding CurrentView}"/>
</Grid>
</UserControl>
This is the ViewModel for the Main window:
using Products.MVVMLibrary;
using Products.MVVMLibrary.Interfaces;
using MyNameSpace.Views;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Input;
namespace MyNameSpace.ViewModel
{
public class ApplicationVM : ObservableObject
{
private MyNameSpace IControlItem currentNavigationItem;
private MyNameSpace ContentControl currentView;
private MyNameSpace List<IControlItem> NavigationList;
private MyNameSpace ICommand changeViewCommand;
private MyNameSpace ViewConverter viewDictionary;
private MyNameSpace Dictionary<string, LinkViewToViewModel> View_ViewModel;
public ApplicationVM()
{
viewDictionary = new ViewConverter();
View_ViewModel = new Dictionary<string, LinkViewToViewModel>();
NavigationList = new List<IControlItem>();
InitialiseLists();
}
private MyNameSpace void AddControlNavigationItems(string name, ContentControl view, ObservableObject viewModel)
{
View_ViewModel.Add(name, new LinkViewToViewModel(view, viewModel));
IControlItem item = (IControlItem)viewModel;
NavigationList.Add(item);
}
private MyNameSpace void InitialiseLists()
{
AddControlNavigationItems("Sales", new SalesForm(), new SalesEntryVM());
AddControlNavigationItems("Purchases", new PurchaseEntryForm(), new PurchasesVM());
AddControlNavigationItems("Setup", new SetupForm(), new SetupFormVM());
//Use the property instead which creates the instance and triggers property change
CurrentViewModel = (IControlItem)View_ViewModel[View_ViewModel.Keys.ElementAt(0)].ViewModel;
CurrentView = View_ViewModel[View_ViewModel.Keys.ElementAt(0)].View;
}
public List<IControlItem> ControlItemsNamesList
{
get => NavigationList;
}
/// <summary>
/// Provides a list of names for Navigation controls to the control item
/// </summary>
public Dictionary<string, LinkViewToViewModel> ApplicationViews
{
get
{
return View_ViewModel;
}
set
{
View_ViewModel = value;
}
}
public ContentControl CurrentView
{
get
{
return currentView;
}
set
{
currentView = value;
OnPropertyChanged("CurrentView");
}
}
public IControlItem CurrentViewModel
{
get
{
return currentNavigationItem;
}
set
{
if (currentNavigationItem != value)
{
currentNavigationItem = value;
OnPropertyChanged("CurrentViewModel");
}
}
}
/// <summary>
/// This property is bound to Button Command in XAML.
/// Calls ChangeViewModel which sets the CurrentViewModel
/// </summary>
public ICommand ChangeViewCommand
{
get
{
if (changeViewCommand == null)
{
changeViewCommand = new ButtonClick(
p => ViewChange((IControlItem)p), CanExecute);
}
return changeViewCommand;
}
}
#region Methods
private MyNameSpace void ViewChange(IControlItem viewname)
{
foreach (KeyValuePair<string, LinkViewToViewModel> item in View_ViewModel)
{
if (item.Key == viewname.ControlName)
{//Set the properties of View and ViewModel so they fire PropertyChange event
CurrentViewModel = (IControlItem)item.Value.ViewModel;
CurrentView = item.Value.View;
break;
}
}
}
private MyNameSpace bool CanExecute()
{
return true;
}
#endregion
}
}
Other classes
public class LinkViewToViewModel
{
public LinkViewToViewModel(ContentControl view, ObservableObject viewModel)
{
View = view;
ViewModel = viewModel;
}
public ContentControl View { get; set; }
public ObservableObject ViewModel { get; set; }
}
I'm most probably missing something obvious as I'm new to WPF development. I'm trying to create a Windows Phone 8.1 application and for my use I want to create a custom user control that contains hero information and hero icon (simple game-related information lookup app).
I have created a usercontrol with name HeroInformationControl and then defined an image and textblock in XAML. Looking up through various resources online I created it as follows:
<UserControl Name="HeroInformationControl"
x:Class="DotaHelper.HeroInformation"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DotaHelper"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="50"
d:DesignWidth="400">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="3*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image
HorizontalAlignment="Left"
Height="Auto"
Stretch="Fill"
VerticalAlignment="Top"
Width="Auto" Source="{Binding ElementName=HeroInformationControl, Path=HeroImage}"
/>
<TextBlock
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
Height="50"
Grid.Column="1"
TextWrapping="Wrap"
Text="{Binding ElementName=HeroInformationControl, Path=HeroName}"
VerticalAlignment="Center"
Width="300"/>
Then, HeroInformation.xaml.cs:
public partial class HeroInformation
{
public HeroInformation()
{
this.InitializeComponent();
this.DataContext = this;
}
public static readonly DependencyProperty HeroNameProperty =
DependencyProperty.Register("HeroName", typeof(string), typeof(string), new PropertyMetadata(""));
public string HeroName
{
get { return (string)GetValue(HeroNameProperty); }
set { SetValue(HeroNameProperty, value); }
}
public static readonly DependencyProperty HeroImageProperty =
DependencyProperty.Register("HeroImage", typeof(string), typeof(string), new PropertyMetadata(""));
public string HeroImage
{
get { return (string)GetValue(HeroImageProperty); }
set { SetValue(HeroImageProperty, value); }
}
}
MainPage.xaml, HeroInformation object:
<local:HeroInformation
x:Name="HeroInformation1"
HorizontalAlignment="Left"
Height="Auto"
VerticalAlignment="Top"
Width="200"
/>
And from the UI thread in MainPage.xaml.cs:
HeroInformation1.HeroImage = hero.IMGurl;
HeroInformation1.HeroName = hero.Heroname;
Sorry for a long code but I have virtually no idea where the problem is.
As a note: hero.IMGUrl and hero.Heroname properties are both of string.
Also if I add to Mainpage.xaml properties by hand (HeroImage and HeroName) it loads.
Any help to understand what's wrong would be appreciated - also, if you spot something that is far from best programming practice I'd be grateful for tips.
Never. Ever. Ever. Do this:
this.DataContext = this;
Instead, give your UserControl an x:Name in your XAML file. Like this:
<UserControl x:Name="usr" ... >
This will allow you to bind to your Dependency Properties using the following binding:
Text="{Binding DataContext.HeroName, ElementName=usr}"
Alternatively, you can bind the UserControl to itself using the following:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
And your binding will look like this:
Text="{Binding HeroName}"
EDIT: Also, as Juan has noticed, your Dependency Property declarations are incorrect:
public string HeroName
{
get { return (string)GetValue(HeroNameProperty); }
set { SetValue(HeroNameProperty, value); }
}
// Using a DependencyProperty as the backing store for HeroName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HeroNameProperty =
DependencyProperty.Register("HeroName", typeof(string), typeof(HeroInformation), new PropertyMetadata(null));
Pro-tip: Use propdp -> Tab -> Tab to declare a dependency property.
the only thing you missed was:
public static readonly DependencyProperty HeroNameProperty =
DependencyProperty.Register("HeroName", typeof(string), typeof(HeroInformation), new PropertyMetadata(""));
typeof(HeroInformation)
the rest is perfect and the way to do it. Might be the image give some issue (I cannot remember in all platforms (WPF,WP, UWP) if it is right use String for URI.
I have a custom usercontrol with DataContext="{Binding RelativeSource={RelativeSource self}}"
On the code behind i've made a dependency property like:
public static DependencyProperty ElementNameProperty = DependencyProperty.Register("ElementName",
typeof(string),
typeof(ElementControl),
new PropertyMetadata(new PropertyChangedCallback((s, e) => { new Base().OnPropertyChanged("ElementName"); })));
public string ElementName
{
get
{
return (string)base.GetValue(ElementNameProperty);
}
set
{
base.SetValue(ElementNameProperty, value);
}
}
Now when I try to use this usercontrol in my mainpage.xaml and use the following binding: <test.TestControl ElementName="{Binding name}" />, it keeps searching for 'name' property in my custom usercontrol instead of where it should come from?
What am I doing wrong ?
It searches there because you have the DataContext set on the topmost level for your user control. What you would need to do is get rid of the relative binding to self in the user control and specify ElementName in bindings (inside user control). Btw you probably don't need OnPropertyChanged in the PropertyChangedCallback cause DependencyProperties in their nature notify about value changes.
I eventually solved it this way. Not the way I wanted, but it's a (in my eyes) pretty neat solution.
CustomUserControl.xaml
<UserControl x:Class="TestApp.Controls.CustomUserControl"
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"
Width="75"
Height="75">
<Canvas x:Name="LayoutRoot"
Background="Black">
<StackPanel Orientation="Vertical">
<Image x:Name="UCImage"
Width="50"
Height="50"
HorizontalAlignment="Center" />
<TextBlock x:Name="UCText"
HorizontalAlignment="Center" />
</StackPanel>
</Canvas>
</UserControl>
CustomUserControl.xaml.cs
public partial class ElementControl : UserControl
{
#region DependencyProperty ElementNameProperty
public static DependencyProperty ElementNameProperty = DependencyProperty.Register("ElementName",
typeof(string),
typeof(ElementControl),
new PropertyMetadata(new PropertyChangedCallback((s, e) =>
{
//See Here
((ElementControl)s).UCText.Text = e.NewValue as string;
})));
public string ElementName
{
get
{
return (string)base.GetValue(ElementNameProperty);
}
set
{
base.SetValue(ElementNameProperty, value);
}
}
#endregion
}