I work an a Windows 8 application which shows a GridView on one page. When ever the user selects an item of this grid and clicks on a button, the next page is loaded with detail information of the selected item.
I am using MVVM for this and have a DelegateCommand from Prims:
public DelegateCommand<Route> ShowRouteDetailsCommand { get; private set; }
This command is initialized inside the constructor:
this.ShowRouteDetailsCommand = new DelegateCommand<Route>(this.ShowRouteDetails);
The navigation is done by Prisms navigation service:
private void ShowRouteDetails(Route route)
{
this.NavigationService.Navigate(PageNames.RouteDetails, route.Id);
}
The routes are shown inside a GridView:
<GridView x:Name="RouteGrid"
ItemsSource="{Binding Routes}"
SelectionMode="Single">
<GridView.ItemTemplate>
<DataTemplate> ...
The command is currently added inside the app bar (just for testing):
<AppBarButton Command="{Binding ShowRouteDetailsCommand}"
CommandParameter="{Binding SelectedValue,
ElementName=RouteGrid, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Icon="Forward" />
My problem is, that the parameter of ShowRouteDetails is allways empty. It doesn't even matter if I try GridViews SelectedValue or SelectedItem property.
I know that I could easily add a SelectedRoute property, bind the SelectedItem to it and use it in ShowRouteDetails but this seems dirty to me.
Why don't you just create a var in your viewModel and bind it to the SelectedItem of the gridView? In this way, when you run the command, you have only to read the value of that var.
<GridView x:Name="RouteGrid" ItemsSource="{Binding Routes}"
SelectionMode="Single" SelectedItem="{Binding myVar}">
<GridView.ItemTemplate>
<DataTemplate>
Related
I have a custom user control like this
<Grid>
<ListView x:Name="LView" SelectedIndex="{Binding SelectedIndex}" ItemsSource="{x:Bind
ItemsSource, Mode=OneWay}" Width="{x:Bind Width}" Height="{x:Bind Height}" VerticalAlignment="Stretch" SelectionMode="Multiple" />
</Grid>
Now in Codebehind, I am trying to get its SelectedIndex using a dependency property
public int SelectedIndex
{
get { return (int)GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
}
public static readonly DependencyProperty SelectedIndexProperty =
DependencyProperty.Register("SelectedIndex", typeof(int), typeof(CustomControl), new PropertyMetadata(0));
And In my main page, I am accessing that dependency property like this
<local:CustomControl Grid.Column="0" Grid.Row="0" Width="400" Loaded="EditTextControl_Loaded"
x:Name="MultiCombo" ></local:CustomControl>
Codebehind
var selIndex = MultiCombo.SelectedIndex;
but neither an event is getting fired on the selectedIndexChange (in the main page) nor I am getting any value on my main page. How can I make this happen?
Note: I have uploaded complete code here
In your CustomControl page, the mode you bind the SelectedIndex property of ListView with SelectedIndex dependency property is OneWay, when you select other items in ListView, the SelectedIndex dependency property won't change, so the value of MultiCombo.SelectedIndex in main page won't change. In this case, you need to set the mode as TwoWay.
CustomControl.xaml:
<ListView x:Name="LView" SelectedIndex="{x:Bind SelectedIndex,Mode=TwoWay}" ItemsSource="{x:Bind ItemsSource, Mode=OneWay}" Width="{x:Bind Width}" Height="{x:Bind Height}" VerticalAlignment="Stretch" SelectionMode="Multiple" />
In your main page, you subscribe the DataContextChanged event to get the SelectedIndex dependency property, but this event only occurs when the DataContext of current page changes. If you want to trigger method in your main page when the selected index of ListView changes, you can define a dependency property in your main page to bind with the SelectedIndex dependency property of CustomControl and add a static callback method that is automatically invoked whenever a property value change is detected. For example:
MainPage.cs:
public int MPSelectedIndex
{
get { return (int)GetValue(MPSelectedIndexProperty); }
set { SetValue(MPSelectedIndexProperty, value); }
}
public static readonly DependencyProperty MPSelectedIndexProperty =
DependencyProperty.Register("MPSelectedIndex", typeof(int), typeof(MainPage), new PropertyMetadata(0, new PropertyChangedCallback(OnDataChanged)));
private static void OnDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MainPage currentPage = d as MainPage;
int count = currentPage.MultiCombo.SelectedIndex;
}
MainPage.xaml:
<local:CustomControl Grid.Column="0" Grid.Row="0" Width="400" Loaded="EditTextControl_Loaded" x:Name="MultiCombo" SelectedIndex="{x:Bind MPSelectedIndex,Mode=TwoWay}" >
</local:CustomControl>
Note:
Since you set the SelectionMode of ListView is Multiple, when you select the first item, the SelectedIndex is 0 and then you also select the second item, the SelectedIndex is still 0. Only when you unselect the first item, the SelectedIndex will change and the method will be triggered.
I think the issue is that you are not binding the SelectedIndex properly.
Instead of binding to self/ ListView's SelectedIndex, you need to bind it to the CustomControl's SelectedIndex DependencyProperty.
<ListView ... SelectedIndex="{Binding
Path=SelectedIndex,
Mode=TwoWay,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type UserControl}}}" .../>
You might need to change the type to your CustomControl's type as necessary (if it is not UserControl).
I have a UserControl with a button inside it. The UserControl its DataContext is one of my models so I can bind to it. However, for the button I want to be able to call a method from my viewmodel. The DataContext of the ListBox is this ViewModel.
Because my ContextMenu also needs the same DataContext I've bound them like this:
Command="{Binding Path=DataContext.AttendEventCommand, ElementName=EventListBox}"
Calling the EventListBox element and using its DataContext to call the AttendEventCommand. However I would like to call the AttendEventCommand from a button on the UserControl. I tried doing it the same way but sadly it doesn't work.
My data context is set like this:
DataContext="{Binding Path=EventList, Source={StaticResource Locator}}
My listbox code:
<ListBox x:Name="EventListBox" ItemsSource="{Binding Occurrences}" Margin="0,50,0,0" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<uctl:EventListItem HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="auto" Height="auto">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu IsZoomEnabled="True" x:Name="ContextMenu">
<toolkit:MenuItem x:Name="Going" Header="{Binding AttendingText}" Command="{Binding Path=DataContext.AttendEventCommand, ElementName=EventListBox}" CommandParameter="{Binding}"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</uctl:EventListItem>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And my UserControl's button:
<Button Grid.RowSpan="3" Grid.Column="5" Opacity="0" Command="{Binding Path=DataContext.AttendEventCommand, ElementName=EventListBox}" CommandParameter="{Binding}"/>
I believe your problem is not that what you want to do is not working; instead, your design appears to be wrong.
What you have now is like this:
You have a Window that has a DataContext and a ListBox. The ListBox has an ItemsSource, which we'll assume is some IEnumerable<Occurrence>.
The appearance of each item in your ListBox is an EventListItem, which is a UserControl that contains at least one Button.
You want this Button's Command to call a method on your Window's DataContext.
This last sentence is wrong. The fact that the item has the button implies that it does something that is related to the item, not the window contents. If this is not true, then the visual design of the window and listbox items should probably be reconsidered.
If the button is in fact affecting the item, then you should not call a method on your Window's DataContext, but instead call a method on your item's DataContext.
The solution is to wrap your model object Occurrence in a view model class of its own. Let's call it OccurrenceViewModel. Your ListBox's ItemsSource would be some form of IEnumerable<OccurrenceViewModel>. Because it's a view model, it's allowed to implement Command methods, which can then in one way or another manipulate the Occurrence, either directly or preferably by passing it to some class that implements the use case.
The DataContext of your EventListItem will be a Model of your ItemsSource because it is part of the DataTemplate. So you have to set it explicitly.
Refer to How to implement a navigation button for some of the code I'll be using as a solution.
Lets assume your custom UserControlis very basic like so:
<UserControl>
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">
<Button Command="{Binding DataContext.SimpleCommand}" CommandParameter="1"></Button>
</Grid>
</UserControl>
Where SimpleCommand is the Command you want to call in the Top View Model.
Then you have to change your DataTemplate to
<DataTemplate>
<Grid>
<uctl:EventListItem
DataContext="{Binding ElementName=myListBox}"/>
<!-- more xaml -->
</Grid>
</DataTemplate>
Your <toolkit:ContextMenu> should work as is.
Put a break point at the Execute function of the ICommand and you will see it will get
hit there everytime.
public void Execute(object parameter)
{
// logic to execute when user hits the command
int debug_var = 0; // put a break point here
}
I am setting the datacontext of my xaml page to a viewmodel passed in to the construrctor. My viewmodel has an object called Item, which has a property called Category.
public DataEntry(DEViewModel vm)
{
InitializeComponent();
this.vm = vm;
this.DataContext = this.vm;
}
I am trying to bind to the ViewModel.Item.Category property like so:
<TextBox Name="txtCategory" Text="{Binding Path=Item.Category, Mode=TwoWay}" />
This does not work. If I set the datacontext to vm.Item, and bind to Category it works.
Any ideas on how to bind to a property that is hanging off of an object on the viewmodel?
Thanks, Terrence
Do it like this,
<TextBox Name="txtCategory"
Text="{Binding Category, Mode=TwoWay}"
DataContext="{Binding Item}" />
The reason it was not working is because it will only look at property notifications for the DataContext, so just set the local DataContext for the control to Item and the control will handle property notifications for Item.
Thanks,
Alex.
My question is the following:
I have a grid and I attached the SelectedIndexChanged event the following way in the xaml file:
"<cc:DetailViewGrid AutoGenerateColumns="False" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="dgAcitivityList" VerticalAlignment="Stretch" ItemsSource="{Binding EntityList}" SelectionMode="Single" IsReadOnly="False">
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="SelectionChanged">
<interactivity:InvokeCommandAction Command="{Binding SelectedItemChangeCommand}" CommandParameter="{Binding SelectedItem, ElementName=dgAcitivityList}"/>
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>"
But I want to attach this event in code behind. I ctreated an own grid that is inherited from windows grid, and I put this code to own control.
public override void OnApplyTemplate()
{
//base.OnApplyTemplate();
System.Windows.Interactivity.EventTrigger selectedItemChangedTrigger = new System.Windows.Interactivity.EventTrigger("SelectionChanged");
System.Windows.Interactivity.InvokeCommandAction action = new System.Windows.Interactivity.InvokeCommandAction();
action.CommandName = "{Binding SelectedItemChangeCommand}";
action.CommandParameter = string.Format("{{Binding SelectedItem, ElementName={0}}}", this.Name);
selectedItemChangedTrigger.Actions.Add(action);
System.Windows.Interactivity.Interaction.GetTriggers(this).Add(selectedItemChangedTrigger);
base.OnApplyTemplate();
}
Is this solution proper? It's not working but I'm not sure that I should put this code in the OnApplyTemplate() method.
I have a form with some validations set in entity metadata class. and then binding entity instance to UI by VM. Something as below:
Xaml like:
<Grid x:Name="LayoutRoot">
<StackPanel VerticalAlignment="Top">
<input:ValidationSummary />
</StackPanel>
<TextBox Text="{Binding Name, Mode=TwoWay}" />
<ComboBox x:Name="xTest" ItemsSource="{Binding MyList}"
SelectedItem="{Binding MyItem,Mode=TwoWay,
DisplayMemberPath="MyName"
ValidatesOnDataErrors=True,
ValidatesOnNotifyDataErrors=True,
ValidatesOnExceptions=True,
NotifyOnValidationError=True,UpdateSourceTrigger=Explicit}" />
</Grid>
Code-behind like:
public MyForm()
{
InitializeComponent();
this.xTest.BindingValidationError +=new EventHandler<ValidationErrorEventArgs>((s,e)=>{
BindingExpression be = this.xTest.GetBindingExpression(ComboBox.SelectedItemProperty);
be.UpdateSource();
if (e.Action == ValidationErrorEventAction.Added)
((ComboBox)s).Foreground = new SolidColorBrush(Colors.Red);
});
}
Metadata like:
[Required]
public string Name { get; set; }
[RequiredAttribute]
public int MyItemID { get; set; }
But when running the app, I got nothing display in valudationSummary.
For CombBox, even there is error, looks like BindingValidationError event is never fired.
How to resolve it?
Why are you using an Explicit UpdateSourceTrigger?
Silverlight validation happens inside the binding framework, when the binding is updating the source object. The way you have this, there won't be a binding validation error because you never tell the binding to update the source object. Well, actually you do, but it happens inside the validation error event handler. You've written chicken-and-egg code.
Remove your UpdateSourceTrigger on your binding or set it to Default.
Remove the explicit call to BindingExpression.UpdateSource.
Remove setting the ComboBox foreground to red - you are using NotifyOnValidationError=True, which eliminates any need to manually color the control.
Remove the DisplayMemberPath from the binding
So your XAML:
<Grid x:Name="LayoutRoot">
<StackPanel VerticalAlignment="Top">
<input:ValidationSummary />
<ComboBox x:Name="xTest" ItemsSource="{Binding MyList}"
SelectedItem="{Binding MyItem,
Mode=TwoWay,
ValidatesOnDataErrors=True,
ValidatesOnNotifyDataErrors=True,
ValidatesOnExceptions=True,
NotifyOnValidationError=True}" />
</StackPanel>
</Grid>
And your code:
public MyForm()
{
InitializeComponent();
// you don't need anything here to have the validations work
}