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
Related
So I have a TabControl that has an instance of ViewModel1 as each tab. ViewModel1's View has a custom UserControl I built, that basically exposes a DependencyProperty "Images" that stores the list of images the control has. This property has been bound (OneWayToSource) to ViewModel1's property "Images".
The problem I'm having is that for some reason, all instances of ViewModel1 (all tabs) are sharing this property. So if Tab1 has 1 image in the control, and Tab2 has 3 images in the control, the "Images" property of each ViewModel1 instance has a collection of 4 images.
I don't know how anything like this could happen - anyone have any ideas?
Note that I'm using Caliburn.Micro as a MVVM framework.
EDIT: The property inside the control is defined like this:
public List<ImageData> Images
{
get { return (List<ImageData>)GetValue(ImagesProperty); }
set { return; }
}
public static readonly DependencyProperty ImagesProperty =
DependencyProperty.Register("Images", typeof(List<ImageData>), typeof(WebImageAlbum),
new UIPropertyMetadata(new List<ImageData>()));
New items are just added to that with Images.Add() and inside the View's XAML, this property is bound to the ViewModel's "Images" property with Mode=OneWayToSource.
EDIT: This is what the Tab view with the UserControl looks like:
<UserControl
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:NET_MD3.Views"
xmlns:cal="http://www.caliburnproject.org"
xmlns:CustomControls="clr-namespace:NET_MD3.CustomControls" x:Class="NET_MD3.Views.AlbumTabView"
mc:Ignorable="d"
d:DesignHeight="267.789" d:DesignWidth="473.684">
<Grid>
<CustomControls:WebImageAlbum x:Name="Album" Margin="10,10,10,50" Width="Auto" Height="Auto" ImageWidthHeight="95"
cal:Message.Attach="[Event ImageClicked] = [Action ImageClicked($eventArgs)]"
Images="{Binding Images, Mode=OneWayToSource}"/>
<Button x:Name="CloseTab" Content="Close tab and delete album" HorizontalAlignment="Left" Margin="10,0,0,10" VerticalAlignment="Bottom" Width="172" Height="30"/>
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="342,243,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>
</Grid>
This is what the ViewModel looks like:
public class AlbumTabViewModel : Screen, IAlbumTabItem
{
#region Constructor
public AlbumTabViewModel(int id)
{
this.TabID = id;
}
#endregion
#region Properties
/// <summary>
/// Get the Images loaded in this tab's Album (do not use the setter - it's for the Binding only)
/// </summary>
public List<ImageData> Images
{
get; set;
}
/// <summary>
/// The Display name of this Screen (Tab)
/// </summary>
public override string DisplayName
{
get
{
return $"Album #{this.TabID}";
}
set { base.DisplayName = value; }
}
/// <summary>
/// The ID of this tab
/// </summary>
public int TabID { get; private set; }
#endregion
#region Actions
/// <summary>
/// Delete this album tab
/// </summary>
public void CloseTab()
{
this.TryClose();
}
/// <summary>
/// An image has been clicked within the album
/// </summary>
/// <param name="e"></param>
public void ImageClicked(ImageData e)
{
//ttt
}
#endregion
}
Turns out the problem was in the declaration of the DependencyProperty. In the UIPropertyMetadata() that I passed to the static declaration of the property, I made it use a 'new List()' as the default value and turns out that you can't really do that, because it will create that List as a static object, which explains why every instance of the UserControl had the same list. This probably happens because the DependencyProperty itself is static and so when it tries to create that default value, it will be static as well.
I guess the lesson is don't put reference types as default values when declaring a new DependencyProperty, and if you must, set it elsewhere (within the CLR property that accesses the value, or something).
Can anyone show me a simple working example for a WPF MVVM application to set the ItemsSource of combobox B based on the SelectedItem of ComboBox A?
It seems from what I've found on this site that it gets all too complicated all too quickly.
What's the "right" MVVM way to get it done?
Thank you.
EDIT
I updated using Didier's example.
An extract of my XAML:
<ComboBox Name="BrowserStackDesktopOS" ItemsSource="Binding Platforms.AvailableBrowserStackDesktopOSes}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopOSSelectedValue, Mode=TwoWay}"/>
<ComboBox Name="BrowserStackDesktopOSVersion" ItemsSource="{Binding Platforms.AvailableBrowserStackDesktopOSVersions}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopOSVersionSelectedValue, Mode=TwoWay}"/>
<ComboBox Name="BrowserStackDesktopBrowser" ItemsSource="{Binding Platforms.AvailableBrowserStackDesktopBrowsers}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopBrowserSelectedValue, Mode=TwoWay}"/>
<ComboBox Name="BrowserStackDesktopBrowserVersion" ItemsSource="{Binding Platforms.AvailableBrowserStackDesktopBrowserVersions}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopBrowserVersionSelectedValue, Mode=TwoWay}"/>
And an example of my code behind:
public string BrowserStackDesktopOSSelectedValue {
get { return (string)GetValue(BrowserStackDesktopOSSelectedValueProperty); }
set { SetValue(BrowserStackDesktopOSSelectedValueProperty, value);
AvailableBrowserStackDesktopOSVersions = AvailableBrowserStackDesktopPlatforms.GetOSVersions(BrowserStackDesktopOSSelectedValue);
NotifyPropertyChanged("BrowserStackDesktopOSSelectedValue");
}
}
However when I select a value for the first ComboBox nothing happens. I am wanting the Itemsource of the next ComboBox to by populated.
What have I done wrong?
Basically you need to expose in your MVVM 2 collections of values for combo-box choices and two properties for selected values.
In the beginning only the first collection if filled with values. When the first selected value changes the second collection will be filled in with appropriate values. Here is an example implementation:
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//Set the data context of the window
DataContext = new TestVM();
}
}
public class TestVM : INotifyPropertyChanged
{
#region Class attributes
protected static string[] firstComboValues = new string[] { "Choice_1", "Choice_2" };
protected static string[][] secondComboValues =
new string[][] {
new string[] { "value_1_1", "value_1_2", "value_1_3" },
new string[] { "value_2_1", "value_2_2", "value_2_3" }
};
#endregion
#region Public Properties
#region FirstSelectedValue
protected string m_FirstSelectedValue;
/// <summary>
///
/// </summary>
public string FirstSelectedValue
{
get { return m_FirstSelectedValue; }
set
{
if (m_FirstSelectedValue != value)
{
m_FirstSelectedValue = value;
UpdateSecondComboValues();
NotifyPropertyChanged("FirstSelectedValue");
}
}
}
#endregion
#region SecondSelectedValue
protected string m_SecondSelectedValue;
/// <summary>
///
/// </summary>
public string SecondSelectedValue
{
get { return m_SecondSelectedValue; }
set
{
if (m_SecondSelectedValue != value)
{
m_SecondSelectedValue = value;
NotifyPropertyChanged("SecondSelectedValue");
}
}
}
#endregion
#region FirstComboValues
protected ObservableCollection<string> m_FirstComboValues;
/// <summary>
///
/// </summary>
public ObservableCollection<string> FirstComboValues
{
get { return m_FirstComboValues; }
set
{
if (m_FirstComboValues != value)
{
m_FirstComboValues = value;
NotifyPropertyChanged("FirstComboValues");
}
}
}
#endregion
#region SecondComboValues
protected ObservableCollection<string> m_SecondComboValues;
/// <summary>
///
/// </summary>
public ObservableCollection<string> SecondComboValues
{
get { return m_SecondComboValues; }
set
{
if (m_SecondComboValues != value)
{
m_SecondComboValues = value;
NotifyPropertyChanged("SecondComboValues");
}
}
}
#endregion
#endregion
public TestVM()
{
FirstComboValues = new ObservableCollection<string>(firstComboValues);
}
/// <summary>
/// Update the collection of values for the second combo box
/// </summary>
protected void UpdateSecondComboValues()
{
int firstComboChoice;
for (firstComboChoice = 0; firstComboChoice < firstComboValues.Length; firstComboChoice++)
{
if (firstComboValues[firstComboChoice] == FirstSelectedValue)
break;
}
if (firstComboChoice == firstComboValues.Length)// just in case of a bug
SecondComboValues = null;
else
SecondComboValues = new ObservableCollection<string>(secondComboValues[firstComboChoice]);
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And the associated XAML
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="window" x:Class="Testing1.MainWindow">
<Grid>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center" Width=" 300">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="10"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox x:Name="FirstOne" ItemsSource="{Binding FirstComboValues}" SelectedItem="{Binding FirstSelectedValue, Mode=TwoWay}"/>
<ComboBox x:Name="SecondOne" ItemsSource="{Binding SecondComboValues}" SelectedItem="{Binding SecondSelectedValue, Mode=TwoWay}" Grid.Row="2"/>
</Grid>
</Grid>
</Window>
As you can see the SelectedValue properties of combo boxes are binded in TwoWay mode so when SelectedValue property of the combo box changes it changes the value on the VM side. And in FirstSelectedValue property setter UpdateSecondComboValues() method is called to update values for the second combo box.
EDIT:
It happens because you mixed both INotifPropertyChanged and DependencyObject. You should choose one of them. Usually you implement INotifyPropertyChanged in your VM and the code in the property setter will work.
If you inherit from DependencyObject however, you should not write any code in the setter/getter. It will never be called by the TwoWay binding. It will just call GetValue(...) internally. To be able to execute an action on DependencyProperty change you should declare it differently with a property changed handler:
#region BrowserStackDesktopOSSelectedValue
/// <summary>
/// BrowserStackDesktopOSSelectedValue Dependency Property
/// </summary>
public static readonly DependencyProperty BrowserStackDesktopOSSelectedValue Property =
DependencyProperty.Register("BrowserStackDesktopOSSelectedValue ", typeof(string), typeof(YourVM),
new FrameworkPropertyMetadata((string)null,
new PropertyChangedCallback(OnBrowserStackDesktopOSSelectedValue Changed)));
/// <summary>
/// Gets or sets the BrowserStackDesktopOSSelectedValue property. This dependency property
/// indicates ....
/// </summary>
public string BrowserStackDesktopOSSelectedValue
{
get { return (string)GetValue(BrowserStackDesktopOSSelectedValue Property); }
set { SetValue(BrowserStackDesktopOSSelectedValue Property, value); }
}
/// <summary>
/// Handles changes to the BrowserStackDesktopOSSelectedValue property.
/// </summary>
private static void OnBrowserStackDesktopOSSelectedValue Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
YourVM target = (YourVM)d;
string oldBrowserStackDesktopOSSelectedValue = (string)e.OldValue;
string newBrowserStackDesktopOSSelectedValue = target.BrowserStackDesktopOSSelectedValue ;
target.OnBrowserStackDesktopOSSelectedValue Changed(oldBrowserStackDesktopOSSelectedValue , newBrowserStackDesktopOSSelectedValue );
}
/// <summary>
/// Provides derived classes an opportunity to handle changes to the BrowserStackDesktopOSSelectedValue property.
/// </summary>
protected virtual void OnBrowserStackDesktopOSSelectedValue Changed(string oldBrowserStackDesktopOSSelectedValue , string newBrowserStackDesktopOSSelectedValue )
{
//Here write some code to update your second ComboBox content.
AvailableBrowserStackDesktopOSVersions = AvailableBrowserStackDesktopPlatforms.GetOSVersions(BrowserStackDesktopOSSelectedValue);
}
#endregion
By the way I always use Dr WPF snippets to write DPs so it goes much faster.
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.
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.
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!