WPF Usercontrol databinding with MVVM ViewModel - wpf

I have spent quite some time on this so seeking help.
Simple data binding with usercontrol with mvvm light does not work.
I have done the following.
Created a MvvmLight (WPF451) project using VS 2015 and named it WpfDataBindingUserControlT1
Added a UserControl and renamed it to SimpleUserControl.xaml
Added some lables as a children(wraped in stackpanel) to the grid inside SimpleUserControl.xaml(All code is given below)
Added a dependency properties in the code behind of the SimpleUserControl.xaml(SimpleUserControl.cs) so that these will help me in databinding.
The data binding simply does not work. I have pulled half of my hair on this so please help. I guess I am missing very simple on this.
The code is as follows.
MainWindows.xaml
<Window x:Class="WpfDataBindingUserControlT1.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:ignore="http://www.galasoft.ch/ignore"
xmlns:local="clr-namespace:WpfDataBindingUserControlT1"
mc:Ignorable="d ignore"
Height="400"
Width="300"
Title="MVVM Light Application"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skins/MainSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<TextBlock FontSize="36"
FontWeight="Bold"
Foreground="Purple"
Text="{Binding WelcomeTitle}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextWrapping="Wrap" />
<local:SimpleUserControl DataContext="{Binding RelativeSource={RelativeSource Self}}" CellValue="{Binding WelcomeTitle}" />
</Grid>
</Window>
MainWindow.cs ( I did not change any thing in this file.)
using System.Windows;
using WpfDataBindingUserControlT1.ViewModel;
namespace WpfDataBindingUserControlT1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
/// <summary>
/// Initializes a new instance of the MainWindow class.
/// </summary>
public MainWindow()
{
InitializeComponent();
Closing += (s, e) => ViewModelLocator.Cleanup();
}
}
}
SimpleUserControl.xaml
<UserControl x:Class="WpfDataBindingUserControlT1.SimpleUserControl"
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:WpfDataBindingUserControlT1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel>
<Label Content="This Prints" />
<Label Name="MyLable" Content="{Binding Path=CellValue}"></Label>
<Label Content="This also Prints" />
</StackPanel>
</Grid>
</UserControl>
SimpleUserControl.cs (added a dependency prop)
using System.Windows;
using System.Windows.Controls;
namespace WpfDataBindingUserControlT1
{
/// <summary>
/// Interaction logic for SimpleUserControl.xaml
/// </summary>
public partial class SimpleUserControl : UserControl
{
public string CellValue
{
get { return (string)GetValue(CellValueProperty); }
set { SetValue(CellValueProperty, value); }
}
public static readonly DependencyProperty CellValueProperty =
DependencyProperty.Register("CellValue", typeof(string), typeof(SimpleUserControl), new FrameworkPropertyMetadata
{
BindsTwoWayByDefault = true,
});
public SimpleUserControl()
{
InitializeComponent();
}
}
}
MainViewModel.cs (I have not changed any thing in this)
using GalaSoft.MvvmLight;
using WpfDataBindingUserControlT1.Model;
namespace WpfDataBindingUserControlT1.ViewModel
{
/// <summary>
/// This class contains properties that the main View can data bind to.
/// <para>
/// See http://www.mvvmlight.net
/// </para>
/// </summary>
public class MainViewModel : ViewModelBase
{
private readonly IDataService _dataService;
/// <summary>
/// The <see cref="WelcomeTitle" /> property's name.
/// </summary>
public const string WelcomeTitlePropertyName = "WelcomeTitle";
private string _welcomeTitle = string.Empty;
/// <summary>
/// Gets the WelcomeTitle property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public string WelcomeTitle
{
get
{
return _welcomeTitle;
}
set
{
Set(ref _welcomeTitle, value);
}
}
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
_dataService.GetData(
(item, error) =>
{
if (error != null)
{
// Report error here
return;
}
WelcomeTitle = item.Title;
});
}
////public override void Cleanup()
////{
//// // Clean up if needed
//// base.Cleanup();
////}
}
}
ViewModelLocator.cs (I have not changed any thing in this as well.)
/*
In App.xaml:
<Application.Resources>
<vm:ViewModelLocatorTemplate xmlns:vm="clr-namespace:WpfDataBindingUserControlT1.ViewModel"
x:Key="Locator" />
</Application.Resources>
In the View:
DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelName}"
*/
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
using WpfDataBindingUserControlT1.Model;
namespace WpfDataBindingUserControlT1.ViewModel
{
/// <summary>
/// This class contains static references to all the view models in the
/// application and provides an entry point for the bindings.
/// <para>
/// See http://www.mvvmlight.net
/// </para>
/// </summary>
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<IDataService, Design.DesignDataService>();
}
else
{
SimpleIoc.Default.Register<IDataService, DataService>();
}
SimpleIoc.Default.Register<MainViewModel>();
}
/// <summary>
/// Gets the Main property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
/// <summary>
/// Cleans up all the resources.
/// </summary>
public static void Cleanup()
{
}
}
}

Add this line to your SimpleUserControl.cs constructor
public SimpleUserControl()
{
InitializeComponent();
(this.Content as FrameworkElement).DataContext = this;
}
You're basically setting the DataContext of the first element in the UserControl.
Jerry Nixon has a great article on this here
UPDATE
forgot to add get rid of the RelativeSource eg
<local:SimpleUserControl CellValue="{Binding WelcomeTitle}" />

Related

WPF setting up Video player from ViewModel

I am working on a WPF application. I need a player to play a file but I need the player in ViewModel as I need it to create snapshot on the player from the ViewModel. This is not working as I can hear the sound but the video is blank.
What am I Missing.
<ContentControl HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Content="{DynamicResource IconSnapShot}" />
Full XAML
<Window x:Class="TestPlayer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestPlayer"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid>
<ContentControl Content="{Binding PlayerFrameworkElement, NotifyOnSourceUpdated=True}" Visibility="{Binding PlayerVisibility}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="Red"/>
</Grid>
</Grid>
</Window>
ViewModel
using Gu.Wpf.Media;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public class PlayerModelView : NotifyPropertyChanged
{
private MediaElementWrapper playerFrameworkElement = new MediaElementWrapper();
private Uri sourcePath;
public PlayerModelView()
{
this.SourcePath = new Uri(#"J:\file.mkv", UriKind.Absolute);
}
public Uri SourcePath
{
get
{
return this.sourcePath;
}
set
{
this.sourcePath = value;
this.PlayerFrameworkElement.Source = null;
this.PlayerFrameworkElement.Source = value;
}
}
public MediaElementWrapper PlayerFrameworkElement
{
get
{
return this.playerFrameworkElement;
}
set
{
this.playerFrameworkElement = value;
this.RaisePropertyChanged(() => this.PlayerFrameworkElement);
}
}
}
}
// --------------------------------------------------------------------------------------------------------------------
//
//
//
// --------------------------------------------------------------------------------------------------------------------
#region
using System;
using System.ComponentModel;
using System.Linq.Expressions;
#endregion
/// <summary>
/// The notify property changed.
/// </summary>
public abstract class NotifyPropertyChanged : INotifyPropertyChanged
{
#region Public Events
/// <summary>
/// The property changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Methods
/// <summary>
/// The raise property changed.
/// </summary>
/// <param name="propertyExpression">
/// The property expression.
/// </param>
/// <typeparam name="TProperty">
/// </typeparam>
protected void RaisePropertyChanged<TProperty>(Expression<Func<TProperty>> propertyExpression)
{
var memberExpression = (MemberExpression)propertyExpression.Body;
var propertyName = memberExpression.Member.Name;
RaisePropertyChanged(propertyName);
}
/// <summary>
/// The raise property changed.
/// </summary>
/// <param name="propertyName">
/// The property name.
/// </param>
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}

What is the best way to diagnose WPF UserControl DependencyProperty issues?

Here is my issue, I created a UserControl as follows:
XAML:
<UserControl x:Class="ProcessVisualizationBar.UserControl1"
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" xmlns:lb="clr-namespace:ProcessVisualizationBar"
Name="ProcessVisualizationBar">
<Border BorderBrush="Silver" BorderThickness="1,1,1,1" Margin="0,5,5,5" CornerRadius="5" Padding="2">
<ListBox Name="ProcessVisualizationRibbon" Grid.Column="1" Height="40" ItemsSource="{Binding ElementName=ProcessVisualizationBar, Path=ItemsSource}"/>
</Border>
</UserControl>
Code Behind(C#):
using System.Windows;
using System.Windows.Controls;
namespace ProcessVisualizationBar
{
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(System.Collections.IEnumerable), typeof(UserControl));
public System.Collections.IEnumerable ItemsSource
{
get { return ProcessVisualizationRibbon.ItemsSource; }
set { ProcessVisualizationRibbon.ItemsSource = value; }
}
public UserControl1()
{
InitializeComponent();
}
}
}
I build my Usercontrol and add the .dll to the reference of another project. I add the reference at the top of my XAML as such:
xmlns:uc="clr-namespace:ProcessVisualizationBar;assembly=ProcessVisualizationBar"
Then I go to use the control.
<uc:UserControl1 Grid.Row="2" x:Name="ProcessVisualizationContent" />
It finds the control okay, but when I try and find the ItemsSource Property I added to it, I'm not finding it. I'm not sure what I missed, and I'm not sure what debug tools are really available to figure this out.
Anyone have some experience with this that can share their wisdom?
What is the actual data being passed? That is what you should be creating and not a pass through situation which you are attempting.
Create a dependency property targetting the actual data to be passed with a property changed handler. On the change event, then call internal code to bind it to the ProcessVisualazation ItemsSource. That way you can debug when the data comes through by placing a breakpoint in the event.
Here is an example where the consumer will see StringData in the Xaml and needs to pas a list of strings into the custom control:
#region public List<string> StringData
/// <summary>
/// This data is to be bound to the ribbon control
/// </summary>
public List<string> StringData
{
get { return GetValue( StringDataProperty ) as List<string>; }
set { SetValue( StringDataProperty, value ); }
}
/// <summary>
/// Identifies the StringData dependency property.
/// </summary>
public static readonly System.Windows.DependencyProperty StringDataProperty =
System.Windows.DependencyProperty.Register(
"StringData",
typeof( List<string> ),
typeof( UserControl ),
new System.Windows.PropertyMetadata( null, OnStringDataPropertyChanged ) );
/// <summary>
/// StringDataProperty property changed handler.
/// </summary>
/// <param name="d">DASTreeBinder that changed its StringData.</param>
/// <param name="e">Event arguments.</param>
private static void OnStringDataPropertyChanged( System.Windows.DependencyObject d, System.Windows.DependencyPropertyChangedEventArgs e )
{
UserControl source = d as UserControl;
List<string> value = e.NewValue as List<string>;
BindDataToRibbon( value );
}
#endregion public List<string> StringData
Now just create a BindDataToRibbon method which will do the dirty work. Note that I use Jeff Wilcox's Silverlight dependency snippets in Visual Studio to generate the above dependency. I have used it for WPF and Silverlight projects.

How do I bind to a custom silverlight control?

I am having problems binding to my control. I would like the label(lblLabel) in my control to display the metadata from whatever is bound to the Field Property. It currently displays "Field" as a label. How do I get it to display "Customer Name :" which is the Name on the view model for property, CustomerName?
My Controls XAML
<UserControl x:Name="ctlRowItem" x:Class="ApplicationShell.Controls.RowItem"
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:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:my="clr-namespace:SilverlightApplicationCore.Controls;assembly=SilverlightApplicationCore"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="g_required" Width="15" />
<ColumnDefinition x:Name="g_label" Width="200" />
<ColumnDefinition x:Name="g_control" Width="auto" />
<ColumnDefinition x:Name="g_fieldEnd" Width="*" />
</Grid.ColumnDefinitions>
<sdk:Label x:Name="lblRequired" Grid.Column="0" Grid.Row="0" />
<sdk:Label x:Name="lblLabel" Grid.Column="1" Grid.Row="3" Target="{Binding ElementName=txtControl}" PropertyPath="Field" />
<TextBox x:Name="txtControl" Grid.Column="2" Grid.Row="3" MaxLength="10" Width="150" Text="{Binding Field, Mode=TwoWay, ElementName=ctlRowItem}" />
</Grid>
</UserControl>
My Controls CODE BEHIND
using System.Windows;<BR>
using System.Windows.Controls;<BR>
using System.Windows.Data;<BR>
using ApplicationShell.Resources;<BR>
namespace ApplicationShell.Controls
{
public partial class RowItem : UserControl
{
#region Properties
public object Field
{
get { return (string)GetValue(FieldProperty); }
set { SetValue(FieldProperty, value); }
}
#region Dependency Properties
public static readonly DependencyProperty FieldProperty = DependencyProperty.Register("Field", typeof(object), typeof(RowItem), new PropertyMetadata(null, Field_PropertyChangedCallback));
#endregion
#endregion
#region Events
#region Dependency Properties
private static void Field_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != e.NewValue)
return;
var control = (RowItem)d;
control.Field = (object)e.NewValue;
}
#endregion
#endregion
#region Constructor
public RowItem()
{
InitializeComponent();
}
#endregion
}
}
View Model
namespace ApplicationShell.Web.ViewModel
{
[Serializable]
public class Customers
{
[Display(AutoGenerateField = false, ShortName="CustomerName_Short", Name="CustomerName_Long", ResourceType = typeof(LocaleLibrary))]
public override string CustomerName { get; set; }
}
}
XAML which calls the My Control
This pages datacontext is set to a property of type Customers (View Model).
<controls:ChildWindow x:Class="ApplicationShell.CustomerWindow"
xmlns:my="clr-namespace:SilverlightApplicationCore.Controls;assembly=SilverlightApplicationCore"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Title="Customer View">
<my:RowItem x:name="test" Field="{Binding CustomerName,Mode=TwoWay}" />
</controls:ChildWindow>
There is a way of getting at the display names of the properties bound to, but sadly it is not trivial and we have to make assumptions about the property-paths used.
I'm aware that the Silverlight Toolkit ValidationSummary is able to find out property names of bindings automatically, but when I looked through its source code, I found that it does this by doing its own evaluation of the binding path.
So, that's the approach I'll take here.
I modified the code-behind of your RowItem user-control, and this is what I came up with:
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
public partial class RowItem : UserControl
{
public RowItem()
{
InitializeComponent();
Dispatcher.BeginInvoke(SetFieldLabel);
}
public string Field
{
get { return (string)GetValue(FieldProperty); }
set { SetValue(FieldProperty, value); }
}
public static readonly DependencyProperty FieldProperty =
DependencyProperty.Register("Field", typeof(string), typeof(RowItem),
null);
/// <summary>
/// Return the display name of the property at the end of the given binding
/// path from the given source object.
/// </summary>
/// <remarks>
/// <para>
/// The display name of the property is the name of the property according
/// to a <see cref="DisplayAttribute"/> set on the property, if such an
/// attribute is found, otherwise the name of the property.
/// </para>
/// <para>
/// This method supports dot-separated binding paths only. Binding
/// expressions such <c>[0]</c> or <c>(...)</c> are not supported and will
/// cause this method to return null.
/// </para>
/// <para>
/// If no suitable property could be found (due to an intermediate value
/// of the property-path evaluating to <c>null</c>, or no property with a
/// given name being found), <c>null</c> is returned. The final property
/// in the path can have a <c>null</c> value, as that value is never used.
/// </para>
/// </remarks>
/// <param name="binding">The binding expression.</param>
/// <param name="source">
/// The source object at which to start the evaluation.
/// </param>
/// <returns>
/// The display name of the property at the end of the binding, or
/// <c>null</c> if this could not be determined.
/// </returns>
private string GetBindingPropertyDisplayName(BindingExpression binding,
object source)
{
if (binding == null)
{
throw new ArgumentNullException("binding");
}
string bindingPath = binding.ParentBinding.Path.Path;
object obj = source;
PropertyInfo propInfo = null;
foreach (string propertyName in bindingPath.Split('.'))
{
if (obj == null)
{
// Null object not at the end of the path.
return null;
}
Type type = obj.GetType();
propInfo = type.GetProperty(propertyName);
if (propInfo == null)
{
// No property with the given name.
return null;
}
obj = propInfo.GetValue(obj, null);
}
DisplayAttribute displayAttr =
propInfo.GetCustomAttributes(typeof(DisplayAttribute), false)
.OfType<DisplayAttribute>()
.FirstOrDefault();
if (displayAttr != null)
{
return displayAttr.GetName();
}
else
{
return propInfo.Name;
}
}
private void SetFieldLabel()
{
BindingExpression binding = this.GetBindingExpression(FieldProperty);
string displayName = GetBindingPropertyDisplayName(binding,
DataContext);
if (lblLabel != null)
{
lblLabel.Content = displayName;
}
}
}
There are a few things to note:
To use this code, your project will need a reference to System.ComponentModel.DataAnnotations. However, that shouldn't be a problem as that's the same reference you need in order to use the Display attribute.
The function SetFieldLabel is called to set the label of the field. I found that the most reliable place to call it was from Dispatcher.BeginInvoke. Calling this method directly from within the constructor or from within a Loaded event handler did not work, as the binding had not been set up by then.
Only binding paths consisting of a dot-separated list of property names are supported. Something like SomeProp.SomeOtherProp.YetAnotherProp are fine, but SomeProp.SomeList[0] is not supported and will not work. If the display name of the binding property cannot be determined, nothing will be displayed.
There's no longer a PropertyChangedCallback on the Field dependency property. We're not really interested in what happens whenever the user changes the text in the control. It's not going to change the display name of the property bound to.
For test purposes, I knocked up the following view-model class:
public class ViewModel
{
// INotifyPropertyChanged implementation omitted.
[Display(Name = "This value is in a Display attribute")]
public string WithDisplay { get; set; }
public string WithoutDisplay { get; set; }
[Display(Name = "ExampleFieldNameKey", ResourceType = typeof(Strings))]
public string Localised { get; set; }
public object This { get { return this; } }
public object TheVerySame { get { return this; } }
}
(The Resources collection Strings.resx contains a single key, with name ExampleFieldNameKey and value This value is in a Resources.resx. This collection also has its Access Modifier set to Public.) I tested out my modifications to your control using the following XAML, with the DataContext set to an instance of the view-model class presented above:
<StackPanel>
<local:RowItem Field="{Binding Path=WithDisplay, Mode=TwoWay}" />
<local:RowItem Field="{Binding Path=WithoutDisplay, Mode=TwoWay}" />
<local:RowItem Field="{Binding Path=Localised, Mode=TwoWay}" />
<local:RowItem Field="{Binding Path=This.This.TheVerySame.This.WithDisplay, Mode=TwoWay}" />
</StackPanel>
This gave me four RowItems, with the following labels:
This value is in a Display attribute
WithoutDisplay
This value is in a Resources.resx
This value is in a Display attribute

How to Bind ListView ItemsSource using XAML not in Code behind.?

I am following MVVM Pattern and I want to bind ListView ItemsSource using XAML, not in even
this.Datacontext = ObservableCollection property.
My code is like this:
<ListView x:Name="MenuBarList"
Grid.Row="2"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Path=Menu.Option}"
Width="{Binding MainMenuWidth}"
SelectedItem="{Binding Path=SelectedMainMenuOption, Mode=TwoWay}" >
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" IsHitTestVisible="False" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
and the Menu is the property and it will seat on the ViewModel. Option is the Class of the Property, so I am using Menu.Option
My Menu is the property of ContentMenuModel type and ContentMenuModel is the class which contain the property of Option and Title and Image.
See the property of the Menu which is inside of the ViewModel
public const string MenuPropertyName = "Menu";
private ContentMenuModel _Menu = null;
/// <summary>
/// Gets the Menu collection.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public ContentMenuModel Menu
{
get
{
return _Menu;
}
set
{
if (_Menu == value)
return;
_Menu = value;
// Update bindings, no broadcast
RaisePropertyChanged(MenuPropertyName);
}
}
And the ContentMenuModel class looks like this:
public class ContentMenuModel
{
#region Title
/// <summary>
/// The <see cref="Title" /> property's name.
/// </summary>
public const string TitlePropertyName = "Title";
private string _Title = String.Empty;
/// <summary>
/// Gets the Title property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
[Required]
[StringLength(128, ErrorMessage = "The Title value cannot exceed 128 characters. ")]
public string Title
{
get
{
return _Title;
}
set
{
if (_Title == value)
{
return;
}
var oldValue = _Title;
_Title = value;
// Update bindings, no broadcast
RaisePropertyChanged(TitlePropertyName);
}
}
#endregion
#region Options
/// <summary>
/// The <see cref="Options" /> property's name.
/// </summary>
public const string OptionsPropertyName = "Options";
private ObservableCollection<ContentMenuOptionModel> _Options = null;
/// <summary>
/// Gets the Options property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public ObservableCollection<ContentMenuOptionModel> Options
{
get
{
return _Options;
}
set
{
if (_Options == value)
{
return;
}
var oldValue = _Options;
_Options = value;
RaisePropertyChanged(OptionsPropertyName);
}
}
#endregion
#region ContextText
/// <summary>
/// The <see cref="Options" /> property's name.
/// </summary>
public const string ContextTextPropertyName = "ContextText";
private ContentPageItem _ContextText = null;
/// <summary>
/// Gets the ContextText property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public ContentPageItem ContextText
{
get
{
return _ContextText;
}
set
{
if (_ContextText == value)
{
return;
}
_ContextText = value;
RaisePropertyChanged(OptionsPropertyName);
}
}
#endregion
}
I had binded the ViewModelLocator to my mainwindow's DataContext and Path=ViewModel's MainMenu, so the MainMain is the object of the ViewModel where I can bind this Property to the ListView's ItemsSource, but it is not working.
Please correct me where I am wrong.
In order to get full example quickly, you can install NUGet for Visual Studio and install MVVMLight package through it on a clean WPF Application project. Then it will set everything up, and you will see how it works.
Though I'll describe the basics here.
MVVMLight supports this out of the box. Standard template for MVVMLigth includes ViewModelLocator and a MainViewModel. ViewModelLocator - is the class that holds all other view models. From the start it has only one property public MainViewModel Main {get;}. ViewModelLocator is registered as a resource in App.xaml
<Application>
<Application.Resources>
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
</Application.Resources>
</Application>
Then, on any page, if you want to get to the view model, you should simply reference Locator resource and get its property that is appropriate for your page. Here's example for MainWindow and its MainViewModel:
<Window DataContext="{Binding Source={StaticResource Locator}, Path=Main}">
<Grid>
<TextBlock Text="{Binding Text}"/>
</Grid>
</Window>
In the example above, I've added public string Text {get;} property to MainViewModel and referenced. In example there is no need for any code-behind, everyting is setup declaratively via xaml.
Simply you add the Grid of you ListView and assing the DataContext to the source and it will be working.

UserControl exposing multiple content properties! How exciting that would be!

I am trying to create a UserControl that will hopefully be able to expose multiple content properties. However, I am ridden with failure!
The idea would be to create this great user control (we'll call it MultiContent) that exposes two content properties so that I could do the following:
<local:MultiContent>
<local:MultiContent.ListContent>
<ListBox x:Name="lstListOfStuff" Width="50" Height="50" />
</local:MultiContent.ListContent>
<local:MultiContent.ItemContent>
<TextBox x:Name="txtItemName" Width="50" />
</local:MultiContent.ItemContent>
</local:MultiContent>
This would be very useful, now I can change ListContent and ItemContent depending on the situation, with common functionality factored out into the MultiContent user control.
However, the way I currently have this implemented, I cannot access the UI elements inside of these content properties of the MultiContent control. For instance, lstListOfStuff and txtItemName are both null when I try to access them:
public MainPage() {
InitializeComponent();
this.txtItemName.Text = "Item 1"; // <-- txtItemName is null, so this throws an exception
}
Here is how I have implemented the MultiContent user control:
XAML: MultiContent.xaml
<UserControl x:Class="Example.MultiContent"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<ContentControl x:Name="pnlList" Grid.Column="0" />
<ContentControl x:Name="pnlItem" Grid.Column="1" />
</Grid>
</UserControl>
Code Behind: MultiContent.xaml.cs
// Namespaces Removed
namespace Example
{
public partial class MultiContent : UserControl
{
public UIElement ListContent
{
get { return (UIElement)GetValue(ListContentProperty); }
set
{
this.pnlList.Content = value;
SetValue(ListContentProperty, value);
}
}
public static readonly DependencyProperty ListContentProperty =
DependencyProperty.Register("ListContent", typeof(UIElement), typeof(MultiContent), new PropertyMetadata(null));
public UIElement ItemContent
{
get { return (UIElement)GetValue(ItemContentProperty); }
set
{
this.pnlItem.Content = value;
SetValue(ItemContentProperty, value);
}
}
public static readonly DependencyProperty ItemContentProperty =
DependencyProperty.Register("ItemContent", typeof(UIElement), typeof(MultiContent), new PropertyMetadata(null));
public MultiContent()
{
InitializeComponent();
}
}
}
I am probably implementing this completely wrong. Does anyone have any idea how I could get this to work properly? How can I access these UI elements by name from the parent control? Any suggestions on how to do this better? Thanks!
You can definitely achieve your goal but you need to take a different approach.
In your solution you're trying to have a dependency property for of a UIElement - and since it never gets set and default value is null that's why you get a NullReference exception. You could probably go ahead by changing the default value from null to new TextBox or something like that but even if it did work it still would feel like a hack.
In Silverlight you have to implement Dpendency Properties yourself. However you've not implemented them as they should be - I tend to use this dependency property generator to do so.
One of the great things about DPs is that they support change notification. So with that in mind all you have to do to get your sample working is define to DPs: ItemContent and ListContent with the same type as Content (object) and when the framework notifies you that either of them has been changed, simply update your textboxes! So here is the code to do this:
MultiContent.xaml:
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<ContentControl x:Name="pnlList" Grid.Column="0" />
<ContentControl x:Name="pnlItem" Grid.Column="1" />
</Grid>
MultiContent.xaml.cs:
namespace MultiContent
{
public partial class MultiContent : UserControl
{
#region ListContent
/// <summary>
/// ListContent Dependency Property
/// </summary>
public object ListContent
{
get { return (object)GetValue(ListContentProperty); }
set { SetValue(ListContentProperty, value); }
}
/// <summary>
/// Identifies the ListContent Dependency Property.
/// </summary>
public static readonly DependencyProperty ListContentProperty =
DependencyProperty.Register("ListContent", typeof(object),
typeof(MultiContent), new PropertyMetadata(null, OnListContentPropertyChanged));
private static void OnListContentPropertyChanged
(object sender, DependencyPropertyChangedEventArgs e)
{
MultiContent m = sender as MultiContent;
m.OnPropertyChanged("ListContent");
}
#endregion
#region ItemContent
/// <summary>
/// ItemContent Dependency Property
/// </summary>
public object ItemContent
{
get { return (object)GetValue(ItemContentProperty); }
set { SetValue(ItemContentProperty, value); }
}
/// <summary>
/// Identifies the ItemContent Dependency Property.
/// </summary>
public static readonly DependencyProperty ItemContentProperty =
DependencyProperty.Register("ItemContent", typeof(object),
typeof(MultiContent), new PropertyMetadata(null, OnItemContentPropertyChanged));
private static void OnItemContentPropertyChanged
(object sender, DependencyPropertyChangedEventArgs e)
{
MultiContent m = sender as MultiContent;
m.OnPropertyChanged("ItemContent");
}
#endregion
/// <summary>
/// Event called when any chart property changes
/// Note that this property is not used in the example but is good to have if you plan to extend the class!
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Called to invoke the property changed event
/// </summary>
/// <param name="propertyName">The property that has changed</param>
protected void OnPropertyChanged(string propertyName)
{
if (propertyName == "ListContent")
{
// The ListContent property has been changed, let's update the control!
this.pnlList.Content = this.ListContent;
}
if (propertyName == "ItemContent")
{
// The ListContent property has been changed, let's update the control!
this.pnlItem.Content = this.ItemContent;
}
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public MultiContent()
{
InitializeComponent();
}
}
}
MainPage.xaml:
<Grid x:Name="LayoutRoot" Background="White">
<local:MultiContent>
<local:MultiContent.ListContent>
<ListBox x:Name="lstListOfStuff" Width="50" Height="50" />
</local:MultiContent.ListContent>
<local:MultiContent.ItemContent>
<TextBox x:Name="txtItemName" Width="50" />
</local:MultiContent.ItemContent>
</local:MultiContent>
</Grid>
This should do the trick!

Resources