I'm trying to list some strings in a Silverlight ListBox. I'm binding a vanilla List to the ItemsSource and then specifying the property of the List item to display in DisplayMemberPath. There is something specific to my implementation that causes the ListBox to display the templated items instead of the property specified inside those items.
Here's the scenario. I have a Parent class that derives from UserControl that adds a "Title" Dependency Property. I create a few Child controls that derive from Parent and specify that inherited Title property. For some reason, binding to that Title property in the ListBox causes the unexpected behavior. Here's the code:
public class Parent : UserControl
{
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
// Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(Parent), new PropertyMetadata(String.Empty));
}
The Child XAML code (Child1 and Child2 are basically the same XAML with trivial codebehinds)
<v:Parent x:Class="TemplateBindingTest.Child1"
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:v="clr-namespace:TemplateBindingTest"
mc:Ignorable="d" Title="Baby 1" Height="41" Width="94">
<Grid x:Name="LayoutRoot" Background="#FFFFBBBB">
<TextBlock HorizontalAlignment="Center" Text="I da baby" VerticalAlignment="Center" FontSize="14" />
</Grid>
The "ViewModel"
public class TheViewModel : INotifyPropertyChanged
{
public List<Parent> Babies { get; set; }
public TheViewModel()
{
Babies = new List<Parent>();
Child1 baby1 = new Child1();
Child2 baby2 = new Child2();
Babies.Add(baby1);
Babies.Add(baby2);
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainPage.xaml
<UserControl x:Class="TemplateBindingTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" xmlns:my="clr-namespace:TemplateBindingTest" Height="268" Width="355">
<Grid x:Name="LayoutRoot" Background="White">
<ListBox ItemsSource="{Binding Path=Babies}" DisplayMemberPath="Title" Height="219" HorizontalAlignment="Left" Margin="12,12,0,0" VerticalAlignment="Top" Width="180" />
<my:Child1 HorizontalAlignment="Left" Margin="203,26,0,0" x:Name="child1" VerticalAlignment="Top" />
<my:Child2 HorizontalAlignment="Left" Margin="203,92,0,0" x:Name="child2" VerticalAlignment="Top" />
</Grid>
So ignoring the fact that it's a bit weird to maintain a list of UI controls in a "viewmodel" class, this is all fairly simple Silverlight. In the ListBox control on MainPage I would expect to see the title for each Child control. Instead, the Child controls themselves show up in the ListBox. What am I missing here? I find it very odd that Silverlight just decides to render the Children controls with no complaints in the Debug Output or other error messages. It's like the DisplayMemberPath attribute gets completely ignored. Could this be a bug in Silverlight?
For ease of testing, here's a link to the full Visual Studio project containing the code above.
This behaviour seems to be by design. If the listbox sees that the Content is a derivative of a UIElement then it makes the simple (and I think reasonable) assumption that you intend for that content to be displayed.
You are right what you are doing is "a bit weird" and you are paying the price. This answer may hold a solution for you. However I would recommend you review the choice to hold an instance of a UserControl in the viewmodel.
#AnthonyWJones is correct and the link he provided led to this answer: I would need to implement my own hacked up version of ListBox in order to achieve the desired functionality mentioned in my question.
public class MyListBox : ListBox
{
protected override bool IsItemItsOwnContainerOverride(object item)
{
return false;
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
((ListBoxItem)element).ContentTemplate = ItemTemplate;
}
}
I guess the moral of this story is don't try to deal with UI elements directly in your ViewModel classes. It's not best practice (and definitely not MVVM) for a reason.
Related
Is it not possible to have multiple layers of UserControls containing ContentControl?
I am trying to create Views for different Models that are derived, so I'd like to eliminate the need to re-create the Views for each object type, and instead provide a ContentControl, or a ContentPresenter to inject the "rest of the view". However, I can only go one level deep with this method.
Here's a simplified example.(I've removed some of the xmlns). In my case, I'm working with significantly more complex Views, and trying to eliminate duplicate code in multiple places, concerned for changes later.
I have a Base UserControl, we'll call it UserControlLevel1
<UserControl x:Class="ContentControlNesting.UserControlLevel1"
x:Name="userControlLevel1"
xmlns:local="clr-namespace:ContentControlNesting">
<StackPanel>
<TextBlock Text="UserControlLevel1ContentTop"/>
<ContentControl Content="{Binding ElementName=userControlLevel1, Path=ChildContent}"/>
<TextBlock Text="UserControlLevel2ContentBottom"/>
</StackPanel>
</UserControl>
It has the following DependencyProperty on the UserControl
namespace ContentControlNesting
{
public partial class UserControlLevel1 : UserControl
{
public UserControlLevel1()
{
InitializeComponent();
}
public static readonly DependencyProperty ChildContentProperty = DependencyProperty.Register("ChildContent", typeof(UIElement), typeof(UserControlLevel1), new PropertyMetadata(null));
public UIElement ChildContent
{
get { return (UIElement)GetValue(ChildContentProperty); }
set { SetValue(ChildContentProperty, value); }
}
}
}
The ContentControl will be used in the following UserControl called UserControlLevel2. This UserControl works fine, just the way I would expect. Or rather UserControlLevel1 works properly within UserControlLevel2.
<UserControl x:Class="ContentControlNesting.UserControlLevel2"
x:Name="userControlLevel2"
xmlns:local="clr-namespace:ContentControlNesting">
<local:UserControlLevel1>
<local:UserControlLevel1.ChildContent>
<StackPanel>
<TextBlock Text="UserControlLevel2ContentTop"/>
<ContentControl Content="{Binding ElementName=userControlLevel2, Path=ChildContent}"/>
<TextBlock Text="UserControlLevel2ContentBottom"/>
</StackPanel>
</local:UserControlLevel1.ChildContent>
</local:UserControlLevel1>
</UserControl>
Likewise, it has a single DependencyProperty for the ContentControl on this UserControl like the first. I've also tried this with differently named DependencyProperties.
namespace ContentControlNesting
{
public partial class UserControlLevel1 : UserControl
{
public UserControlLevel1()
{
InitializeComponent();
}
public static readonly DependencyProperty ChildContentProperty = DependencyProperty.Register("ChildContent", typeof(UIElement), typeof(UserControlLevel1), new PropertyMetadata(null));
public UIElement ChildContent
{
get { return (UIElement)GetValue(ChildContentProperty); }
set { SetValue(ChildContentProperty, value); }
}
}
}
Okay, so at this point, everything seems to be working fine. I've added additional content inside of the ContentControl of UserControlLevel1, and I've added another ContentControl within my UserControlLevel2 UserControl.
The problem is when I try to introduce a 3rd Tier of either UserControl or my MainWindow. Anything I add to the ContentControl of UserControlLevel2 just does not appear.
<Window x:Class="ContentControlNesting.MainWindow"
xmlns:local="clr-namespace:ContentControlNesting"
Title="MainWindow" Height="200" Width="300">
<local:UserControlLevel2>
<local:UserControlLevel2.ChildContent>
<StackPanel>
<TextBlock Text="Main Window Content Text"/>
</StackPanel>
</local:UserControlLevel2.ChildContent>
</local:UserControlLevel2>
</Window>
Am I trying to do something that's not possible? Or am I doing something wrong with ContentControl and the DependencyProperties? Should I be looking at this with a different approach?
It is possible. The system cannot resolve the ElementName in the Binding. The solution is to use the relative binding. Just replace the following line in UserControlLevel2 and your are done:
<ContentControl Content="{Binding Path=ChildContent, RelativeSource={RelativeSource AncestorType={x:Type local:UserControlLevel2}}}"/>
Part of my app has a month-view calendar interface, but I'm having trouble with item selection. The interface is set up so that each of the days in the view contains a ListBox of items, much like the month view in Outlook. The problem I'm experiencing is that I need to maintain a single item selection across all of the ListBoxes.
Below is a sample that should adequately describe my situation. I need to maintain a single selection between both ListBoxes.
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Numbers}"
SelectedItem="{Binding SelectedObject"/>
<ListBox Grid.Column="1" ItemsSource="{Binding Dates}"
SelectedItem="{Binding SelectedObject"/>
</Grid>
</Window>
And the view model for the window:
class MainWindowViewModel : DependencyObject
{
public static readonly DependencyProperty SelectedObjectProperty =
DependencyProperty.Register("SelectedObject", typeof(object),
typeof(MainWindowViewModel),
new UIPropertyMetadata(null));
public ObservableCollection<int> Numbers { get; set; }
public ObservableCollection<DateTime> Dates { get; set; }
public object SelectedObject
{
get { return GetValue(SelectedObjectProperty); }
set { SetValue(SelectedObjectProperty, value); }
}
}
In this primitive example, I would expect that when the SelectedObject property of my view model gets set to an item that's not in one ListBox, the selection would be removed in that ListBox, but that doesn't happen. I understand that I can simply name each ListBox, and hook into the SelectionChanged event. I'd prefer to not have to do that with an entire month-view calendar. There has to be a better way.
In a previous iteration of the app, I was able to create a SelectionManager static class with an attached property that was used to maintain selection. However, I can't use this now as the classes I'm using for my items are not DependencyObjects, and I'd really prefer not to have to create DependencyObject wrapper classes as this will considerably complicate my architecture.
Thanks.
EDIT 1: Added a view model class as requested.
Bind both ListBoxes to the same collection (make an observable collection of DatesAndNumber objects or something along those lines) and use converters and/or data templates to get the desired output. Then you can simply set IsSynchronizedWithCurrentItem to true on both ListBoxes to get the desired effect.
Reference: http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.selector.issynchronizedwithcurrentitem.aspx
how about using ICollectionView and its associated filter ?
here is a link which explains it better
http://marlongrech.wordpress.com/2008/11/22/icollectionview-explained/
I figured it out by creating a separate selection manager class and an ISelectable interface. You can read about the details on CodeProject.
I have created a user control that consists of a expander, listbox and checkboxes. I am not able to access the checkboxes (child control) and I want to generate the number of expanders based on the number of rows in a table dynamically. Can anyone suggest the possible solutions to
This is extremely vague. In most cases you would just expose some of the internal control's properties, e.g. if you want to create dynamic content you would expose the ItemsSource and ItemTemplate of an internal ListBox of whatever you use so it can be set from outside, e.g.
<UserControl x:Class="Test.UserControls.Bogus" 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" Name="control">
<StackPanel>
<TextBlock Text="Lorem Ipsum:" />
<ItemsControl ItemsSource="{Binding ElementName=control, Path=ItemsSource}"
ItemTemplate="{Binding ElementName=control, Path=ItemTemplate}" />
</StackPanel>
</UserControl>
public partial class Bogus : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty = ItemsControl.ItemsSourceProperty.AddOwner(typeof(Bogus));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemTemplateProperty = ItemsControl.ItemTemplateProperty.AddOwner(typeof(Bogus));
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
public Bogus()
{
InitializeComponent();
}
}
Usage:
<uc:Bogus ItemsSource="{Binding Data}">
<uc:Bogus.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Foreground="Red" />
</DataTemplate>
</uc:Bogus.ItemTemplate>
</uc:Bogus>
You can of course also encapsulate a lot of logic which you do not need exposed.
Since you want a varying amount of expanders you might have an ItemsControl (unlike a ListBox it has no selection) which already defines an ItemTemplate which contains an expander. You probably can also create a partial template as shown in this answer of mine.
Sounds like you need to navigate the visual tree. The simplest way of doing this is via Linq-to-VisualTree. To find all the CheckBoxes that are a child of 'this', use the following query:
IEnumerable<CheckBox> checks = this.Descendants<CheckBox>().Cast<CheckBox>();
Your application is running in an Application instance. Access the usercontrol components with Application.usercontrol.ComponentName if it is not a UI update. If you make UI updates, you have to run the access in a separate dispatcher thread. In that case, use BackgroundWorker.
For example, I am running my main application class MainWindow and accessing it as,
MainWindow rootWindow = Application.Current.MainWindow as MainWindow;
Now access the usercontrol and properties of components as:
rootWindow.usercontrolX.ComponentY.PropertyZ
Define properties in the child's class for each of those controls. You will be able to access them from the Parent User Control, assuming you have added the Child User Control within the Parent User Control.
Parent User Control.. SingalData is the child User Contol
<my:C1TabItem Header="Signal">
<local:SignalData Width="1036" OnSignalNameChange="SignalInputTab_OnSignalNameChange" Loaded="SignalInputTab_Loaded" Height="353" VerticalAlignment="Top" MinHeight="353" HorizontalAlignment="Left"></local:SignalData>
In the Child User Contorl class, if you have a component named tabProductList you add a property -
public C1.WPF.C1TabControl TabProductList
{
get { return this.tabProductList; }
}
And finally, from your parent class you can reference it as -
C1TabItem tbItem = (C1TabItem)c1TabControl1.SelectedItem;
SignalData sigInp = (SignalData)tbItem.Content;
if (sigInp.TabProductList.SelectedIndex == 0)
{
....
I have a simple application with just a window and a user control. The user control has a list box. The user control is positioned on the Window and I want to bind the user control's listbox to an element on the window's data context.
The examples I've been able to find have CLR properties on the user control which are accessed in code not via XAML.
<Window x:Class="WpfApplication2b.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication2b="clr-namespace:WpfApplication2b" Title="MainWindow" Height="410" Width="520">
<Grid>
<WpfApplication2b:MyUserControl></WpfApplication2b:MyUserControl>
</Grid>
And here is the user control itself.
<UserControl x:Class="WpfApplication2b.MyUserControl"
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 Background="#FFD8AA13">
<ListBox Height="276" HorizontalAlignment="Left" Margin="12,12,0,0" Name="listBox1" VerticalAlignment="Top" Width="276" />
</Grid>
As you can see it's just a listbox on a different coloured background. I have no idea where to go next :)
I'm guessing that I need to add a code behind property for the list box as a dependency property?
Edit: I've added a dependencyProperty, but I don't think I've quite got the point.
public partial class MyUserControl : UserControl
{
public static readonly DependencyProperty ListBoxProperty;
static MyUserControl()
{
FrameworkPropertyMetadata md = new FrameworkPropertyMetadata();
MyUserControl.ListBoxProperty = DependencyProperty.Register("MyListBox", typeof (ListBox),
typeof (MyUserControl), md);
}
public ListBox MyListBox
{
get
{
return (ListBox) GetValue(ListBoxProperty);
}
set
{
SetValue(ListBoxProperty, value);
}
}
public MyUserControl()
{
InitializeComponent();
}
}
Your UserControl will inherit the DataContext from the Window so you can bind properties on the ListBox as though it were declared in the Window. To make the control more flexible you can declare Dependency Properties for the data items from the DataContext that you want to use (i.e. an ItemsSource collection) and pass them into the control, rather than passing the ListBox out.
I think this question/answer is almost what you're looking for. Essentially you're going to need to make a dependency property (using the AddOwner registration method) and set up the DataBinding on the ListBox's ItemsSource to hook to the Dependency Property. The example in the answer does the same thing for a ComboBox, and should be almost the same for a ListBox.
Exposing inner Control properties for binding in WPF
I am exploring the Silverlight attached behaviors mechanism in order to use the Model-View-ViewModel pattern within my Silverlight applications. To start with, I am trying to get a simple Hello World working, but I am completely stuck in an error for which I'm not able to find a solution.
What I have right now is a page that just contains a button which should display a message when clicked. The click event is handled by using a class derived from Behavior, and the message is specified as a dependency property of the behavior itself. The problem comes when trying to bind the message property to a property on a viewmodel class used as the data context: I get an exeption in the call to InitializeComponent in the view.
Here is all the code I'm using, as you can see it is rather simple. First the markup of the main page and the view it contains:
MyPage
<UserControl x:Class="MyExample.MyPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyExample"
>
<Grid x:Name="LayoutRoot">
<local:MyView/>
</Grid>
</UserControl>
MyView (the TextBlock is there just to check that the binding syntax is correct)
<UserControl x:Class="MyExample.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:MyExample"
Width="400" Height="300">
<StackPanel Orientation="Vertical" x:Name="LayoutRoot" Background="White">
<StackPanel.Resources>
<local:MyViewmodel x:Key="MyResource"/>
</StackPanel.Resources>
<TextBlock Text="This button will display the following message:"/>
<TextBlock Text="{Binding MyMessage, Source={StaticResource MyResource}}" FontStyle="Italic"/>
<Button x:Name="MyButton" Content="Click me!">
<i:Interaction.Behaviors>
<local:MyBehavior Message="{Binding MyMessage, Source={StaticResource MyResource}}"/>
</i:Interaction.Behaviors>
</Button>
</StackPanel>
</UserControl>
Now the code, there are two classes: one for the behavior and another one for the viewmodel:
MyViewmodel
public class MyViewmodel
{
public string MyMessage
{
get { return "Hello, world!"; }
}
}
MyBehavior
public class MyBehavior : Behavior<Button>
{
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message",
typeof(string), typeof(MyBehavior),
new PropertyMetadata("(no message)"));
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Click += new RoutedEventHandler(AssociatedObject_Click);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Click -= new RoutedEventHandler(AssociatedObject_Click);
}
void AssociatedObject_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Message);
}
}
Simple enough, but this code throws an AG_E_PARSER_BAD_PROPERTY_VALUE [Line: 15 Position: 43] (right at the start of the value being set for the Message property) exception when ran. I'm sure that I'm missing something, but what?
Additional information: if I remove the binding from the Message property on MyBehavior (that is, if I set its value to any static string), it works fine. Also, I'm targeting silverlight 3 RTW.
Thanks a lot!
UPDATE
It seems that unlike WPF, Silverlight does not support data binding on any object deriving from DependencyObject, but only on objects deriving from FrameworkElement. This is no the case for Behavior, hence binding does not work.
I have found a workaround here, in the form of something named surrogate binders. Basically you specify the element and property to be binded, as well as the value, as attributes of the FrameworkElement containing the non-FrameworkElement object.
UPDATE 2
The surrogate binder does not work when the FrameworkElement contains an Interaction.Behaviors sub-element.
I have found another solution here, and this one seems to work. This time, the trick used is a DeepSetter. You define one of such setters as a static resource on the containing StackPanel, and then reference the resource from the behavior. So in my example, we should expand the StackPanel resources section as follows:
<StackPanel.Resources>
<local:MyViewmodel x:Key="MyResource"/>
<local:DeepSetter
x:Key="MyBehaviorSetter"
TargetProperty="Message"
BindingExport="{Binding MyMessage, Source={StaticResource MyResource}}"/>
</StackPanel.Resources>
...and modify the button's behavior declaration as follows:
<local:MyBehavior local:DeepSetter.BindingImport="{StaticResource MyBehaviorSetter}"/>
UPDATE 3
Good news: data binding for any DependecyObject will be available on Silverlight 4: http://timheuer.com/blog/archive/2009/11/18/whats-new-in-silverlight-4-complete-guide-new-features.aspx#dobind
To get the DataBinding support the class should inherit from FrameworkElement.Hoping MSFT will give support in Silverlight 4