I'm writting an app in WP7 (Silverlight 3). I have a view model that looks like this
public class MainViewModel
{
public List<ActivityTypes> ActivityTypes{get;set;}
public RelayCommand LoadActivity{get;set;}
}
My pages datacontext is set to the view model and I have a listbox with it's item source set to the ActivityTypes collection. In the listbox I'm trying to render a list of buttons who's command is bound to the LoadActivity property on the viewmodel. RelayCommand is part of the MVVM Light toolkit in case you are wondering what it is.
The problem I am having is I can't find a way to bind my button command to the LoadActivity property as my listbox has it's itemsource set to the Activitytypes collection and this property is in the parent. I've read about FindAncester but it doesn't look like this is supported in Silverlight 3.
My XAML looks like this
<UserControl.Resources>
<DataTemplate x:Key="ActivityTypeListTemplate">
<StackPanel>
<Button Command="{Binding LoadActivity}"> <!--binding not found as we're in the collection-->
<TextBlock Text="{Binding Name}" FontSize="50"/>
</Button>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">
<ListBox Margin="0" ItemsSource="{Binding ActivityTypes}" ItemTemplate="{StaticResource ActivityTypeListTemplate}"/>
</Grid>
What's the best way to code something like this?
There is no "direct" way to do this. You can set the Buttons DataContext to your MainViewModel (preferably as a StaticResource link) and the it would work.
Take a look at Bind to parent object in xaml
Related
I have a control that is set up as a DataTemplate:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<DataTemplate x:Key="KEYBOARD_EN">
<StackPanel>
<Button Visibility="{Binding Path=RegisterButtonVisible}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
</StackPanel>
</DataTemplate>
In this DataTemplate there is a control on which I wish to set the Visibility from various view models:
<Button Visibility="{Binding Path=RegisterButtonVisible}" Style="{StaticResource ...} > Register </Button>
I do routed events with my control, so I tried to set up something similar, but no matter what I try, the RegisterButtonVisible property does not get picked up:
public partial class MainKeyboard : UserControl
{
public static DependencyProperty RegisterButtonVisibleProperty;
public Visibility RegisterButtonVisible
{
get { return (Visibility)GetValue(RegisterButtonVisibleProperty); }
set { SetValue(RegisterButtonVisibleProperty, value); }
}
static MainKeyboard()
{
RegisterButtonVisibleProperty = DependencyProperty.Register("RegisterButtonVisible", typeof (Visibility),
typeof (MainKeyboard));
}
}
In my ViewModel I do this:
public Visibility RegisterButtonVisible // get, set, raisepropchange, etc
My DataTemplate with the button in it is wrapped in a userControl:
<UserControl x:Class="Bleh.Assets.MainKeyboard"
x:Name="TheControl"
Unloaded="UserControl_Unloaded">
<Viewbox>
<Grid>
<ContentControl Name="ctrlContent" Button.Click="Grid_Click" />
</Grid>
</Viewbox>
and is used in my views like this:
<assets:MainKeyboard
RegisterButtonVisible="Collapsed"
Loaded="MainKeyboard_Loaded">
<b:Interaction.Triggers>
<b:EventTrigger EventName="Register">
<b:InvokeCommandAction Command="{Binding ConfirmEmailAddressCommand}"/>
</b:EventTrigger>
<b:EventTrigger EventName="Enter">
<b:InvokeCommandAction Command="{Binding EnterKeyCommand}"/>
</b:EventTrigger>
</b:Interaction.Triggers>
</assets:MainKeyboard>
Please note this attribute:
RegisterButtonVisible="Collapsed"
This is my dependency property. It shows up in intelliesense, so the CLR has registered it correctly, but it does NOT pick up the property assignment (Collapsed is ignored).
This makes me feel like it is very close, but I do remember someone telling me I can not do this, thus the EventTriggers (this is a common issue with datatemplates and MVVM apparently).
So one option is to use something in the Interaction namespace, like I do my event triggers ( I just need to fire a "Visibility" trigger on this button somehow, at least I figure).
What is the right ANY way to do this in MVVM?
Fixing your code
In order to make your existing code work, you need to tell need to tell WPF what object RegisterButtonVisible should be read from. If it's a user control, give the UserControl a name and then reference that element via ElementName, like so:
<UserControl ... lots of stuff here
x:Name="TheControl"
>
In your button binding:
<Button Visibility="{Binding ElementName=TheControl, Path=RegisterButtonVisible}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
Of course, if you can't do that because the button and the usercontrol are in different files, you can still use an ancestor binding:
<Button Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type assets:MainKeyboard}},
Path=RegisterButtonVisible}"
Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
which, for each button, will walk up to find the closest instance of assets:MainKeyboard and then bind to the RegisterButtonVisible property.
Using MVVM
If you want to achieve the same using MVVM (instead of on a control), you need to use a converter to convert a boolean to a visibility property, like so:
<Button Visibility="{Binding IsRegistrationAllowed, Converter={StaticResource BoolToVis}}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
Of course, that assumes that your DataContext is set up correctly and pointing at your ViewModel.
I'm a newcomer to Caliburn.Micro and there are a few things I'm still not getting.
ViewModel first:
First of is a ViewModel that manages a Collection of other ViewModels:
public class NavigationBarViewModel : PropertyChangedBase
{
public BindableCollection<IHaveDisplayName> Items { get; set; }
}
I've got a ItemsControl (it's Telerik RadOutlookBar if that matters) as the root of a UserControl
of that view and I set the ItemTemplate too ensure that the ViewModels I insert into the collection are wrapped in a corresponding RadOutlookBarItem ( should I use ItemContainer instead of ItemTemplate here? ).
<telerik:RadOutlookBar x:Name="Items">
<telerik:RadOutlookBar.TitleTemplate>
<DataTemplate>
<ContentControl Content="{Binding Path=DisplayName}" />
</DataTemplate>
</telerik:RadOutlookBar.TitleTemplate>
<telerik:RadOutlookBar.ItemTemplate>
<DataTemplate>
<telerik:RadOutlookBarItem cal:Bind.Model="{Binding}"
Header="{Binding Path=DisplayName}">
<ContentControl />
</telerik:RadOutlookBarItem>
</DataTemplate>
</telerik:RadOutlookBar.ItemTemplate>
</telerik:RadOutlookBar>
This way I wan't the ViewModels in the collection to appear where the ContentControl is. I Bind the model to the root item of the DataTemplate to ensure conventions will work but have no idea how to bind to the ContentControl with convention. The DataContext inside the DataTemplate is of course the ViewModel itself. Using normal WPF standard I would put Content="{Binding}".
Now the model is there inside the RadOutlookBarItem but it's view doesn't get applied. Not even View can't be found, only a string with the class name.
Isn't this the proper way to do this?
As I answered here: Dynamic Telerik RadOutlookBar headers come out wrong with ItemTemplate in which I thought was a unrelated matter I was using the wrong property. ItemTemplate controls the picker and contentTemplate what comes up when you select. Here is the code that works:
<telerik:RadOutlookBar x:Name="Items">
<telerik:RadOutlookBar.ContentTemplate>
<DataTemplate >
<ContentControl cal:View.Model="{Binding}" />
</DataTemplate>
</telerik:RadOutlookBar.ContentTemplate>
<telerik:RadOutlookBar.TitleTemplate>
<DataTemplate>
<TextBlock x:Name="DisplayName"
cal:Bind.Model="{Binding}" />
</DataTemplate>
</telerik:RadOutlookBar.TitleTemplate>
<telerik:RadOutlookBar.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="DisplayName"
cal:Bind.Model="{Binding}" />
</DataTemplate>
</telerik:RadOutlookBar.ItemTemplate>
</telerik:RadOutlookBar>
I am using Silverlight 4 and the MVVM pattern.
My view model has two properties:
SomeProperty and
MyCommand
SomeProperty is a complex type and has a lot of subproperties. MyCommand is a property to handle commanding from a Button.
I have a child window (the view) with a Grid as the LayoutRoot which is bound to the SomeProperty property of the view model.
<Grid x:Name="LayoutRoot" DataContext="{Binding SomeProperty, Mode=TwoWay}">
...
</Grid>
However, inside the Grid I want to bind a Button's Command property to the MyCommand property of the view model:
<Button Command={Binding MyCommand} />
But this is not working because MyCommand is a property of the view model, and not a property of the view model's SomeProperty property. (When I click on the Button it does not execute the command.)
Anywho, is there a way using data binding in Silverlight 4 such that I can have a container UI element set its DataContext property explicitly, but then have a different control within the container reference a property that's a sibling (or parent or whatever) of the DataContext of the containing control?
My current workaround is to define the binding in the view's class, but I'd rather have it in the XAML.
Thanks
If you give your root element (ChildWindow, UserControl, whatever) a name, then you can use ElementName to get to the view model.
<UserControl x:Name="MyUserControl">
<Grid x:Name="LayoutRoot" DataContext="{Binding SomeProperty, Mode=TwoWay}">
<Button Command="{Binding MyCommand}" DataContext="{Binding DataContext, ElementName=MyUserControl}" />
</Grid>
</UserControl>
Or, here's another way to do the same thing.
<UserControl x:Name="MyUserControl">
<Grid x:Name="LayoutRoot" DataContext="{Binding SomeProperty, Mode=TwoWay}">
<Button Command="{Binding DataContext.MyCommand, ElementName=MyUserControl}" />
</Grid>
</UserControl>
You try add datacontext to binding? The datacontext have to point to your viewmodel, because the default data context is a parent control or parent data context, in this case your layout root.
See this
and this
I hope this help.
Regards.
I use a version of the BindableProxy described in this post:
http://weblogs.asp.net/dwahlin/archive/2009/08/20/creating-a-silverlight-datacontext-proxy-to-simplify-data-binding-in-nested-controls.aspx
Above your Grid (probably within the UserControl.Resources) you would create:
<UserControl.Resources>
<ns:BindableProxy x:Key="BindableProxy" />
<UserControl.Resources>
Then, in the button binding:
<Button Command="{Binding DataSource.MyCommand, Source={StaticResource BindableProxy}}" />
I need some quick help which is a road blocker for me now. I have Button in ItemsControl and I need to perform some task on Button click. I tried adding Command to Button in ItemsControl DataTemplate but its not working. Can anyone suggest how to proceed further.
<UserControl.Resources>
<DataTemplate x:key="mytask">
<TextBox Grid.Row="5" Grid.Column="2" Text="{Binding Path=PriorNote}" Grid.ColumnSpan="7" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,5" Width="505" Foreground="Black"/>
<StatusBarItem Grid.Row="2" Grid.Column="8" Margin="8,7,7,8" Grid.RowSpan="2">
<Button x:Name="DetailsButton" Command="{Binding CommandDetailsButtonClick}">
</DataTemplate>
</UserControl.Resources>
<Grid>
<ItemsControl Grid.Row="1"
ItemsSource="{Binding ListStpRules}"
ItemTemplate="{StaticResource myTaskTemplate}" Background="Black"
AlternationCount="2" >
</ItemsControl>
</Grid>
and in ViewModel I have implemented code for Command. And its not working. Please suggest any solution for me to proceed further
The DataContext of each item in your ItemsControl is the item in the collection the ItemsControl is bound to. If this item contains the Command, your code should work fine.
However, this is not usually the case. Typically there is a ViewModel containing an ObservableCollection of items for the ItemsControl, and the Command to execute. If this is your case, you'll need to change the Source of your binding so it looks for the command in ItemsControl.DataContext, not ItemsControl.Item[X]
<Button Command="{Binding
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}},
Path=DataContext.MyCommand}" />
If your ViewModel has a property of type ICommand you can bind the Button's Command property to that:
XAML:
<DataTemplate DataType="{x:Type my:FooViewModel}">
<Button Content="Click!" Command="{Binding Path=DoBarCommand}" />
</DataTemplate>
C#:
public sealed class FooViewModel
{
public ICommand DoBarCommand
{
get;
private set;
}
//...
public FooViewModel()
{
this.DoBarCommand = new DelegateCommand(this.CanDoBar, this.DoBar);
}
}
Read this:
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
Implement a class similar to RelayCommand in the above article. Would make your further MVVM coding easier. :-)
Just a guess. Is CommandDetailsButtonClick defined in a ViewModel, which is DataContext of your UserControl (the one with ListStpRules property)?
DataContext of button in ItemTemplate is an item from ListStpRules, and if you command is not there then binding won't find it.
You can check diagnostic messages from wpf in Output window while debugging your application. It writes there if it can not resolve binding.
I have a Listbox with a UserControl as the DataTemplate. This UserControl has a Button to remove that item from the list.
<ListBox x:Name="FileList" ItemsSource="{Binding Files}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Views:FileItem/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The ItemsSource is defined as:
ObservableCollection<FileViewModel> m_fileViews = new ObservableCollection<FileViewModel>();
Here is the UserControl simplified:
<UserControl x:Class="Views.FileItem">
<Canvas x:Name="LayoutRoot">
<TextBlock x:Name="FileName" Text="{Binding FileName}" />
<Button Content="Remove"/>
</Canvas>
</UserControl>
When the user clicks the Remove button, it should remove this item from the ObservableCollection.
The problem is, the DataContext for each ListBoxItem is a different ViewModel than the ViewModel that holds the ObservableCollection.
I'm not sure how to bind the Remove button to an ICommand in the "parent" ViewModel. Any help would be appreciated. Thanks so much.
I would bind the button to an ICommand in the UserControl's ViewModel, then send a message across to the parent ViewModel using loosely-coupled messaging (available in most Mvvm frameworks, like MvvmFoundation)
Let the parent VM register for the 'remove me' message, and update the ObservableCollection accordingly...
Hope this helps :)