3

The XAML below is basically trying to make a list of Buttons (rendered from the Name property of objects in the Views collection in the current DataContext.

When I click on a button the CurrentItem property of CollectionViewSource should change and the associated View should be displayed in a content presenter.

OK. If I click in the ListBox in the XAML below it works exactly as desired.

But, If I click a button in the UniformGrid (created by the items control) the CurrentItem property is not updated.

How do I get the CurrentItem to be updated when an item is selected in the ItemsControl?

Thanks

<UserControl x:Class="Pos.Features.Reservation.ReservationView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:product="clr-namespace:Pos.Features.ProductBrowser"
             xmlns:activity="clr-namespace:Pos.Features.ActivityBrowser"
             xmlns:addbysku="clr-namespace:Pos.Features.AddBySku"
             xmlns:client="clr-namespace:Pos.Features.ClientBrowser"
             xmlns:notes="clr-namespace:Pos.Features.Notes"
             xmlns:controls="clr-namespace:Pos.Views"
             xmlns:res="clr-namespace:Pos.Core;assembly=Pos.Core"
             Height="300" Width="300">
    <UserControl.Resources>
        <DataTemplate DataType="{x:Type product:ProductBrowserViewModel}">
            <product:ProductBrowserView/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type activity:ActivityBrowserViewModel}">
            <activity:ActivityBrowserView/>
        </DataTemplate>

        <CollectionViewSource x:Name="x" x:Key="ViewsCollection" Source="{Binding Views}"  />
    </UserControl.Resources>

    <StackPanel>
        <ListBox Name="ListBoxMenu" Grid.Column="0" Margin="5" ItemsSource="{Binding Source={StaticResource ViewsCollection}}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}" Padding="10"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <ContentControl Grid.Column="1" Content="{Binding ElementName=ListBoxMenu, Path=SelectedItem}"/>
        <ItemsControl  Grid.Column="2" Name="ViewList" ItemsSource="{Binding Source={StaticResource ViewsCollection}}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Button>
                        <TextBlock Text="{Binding Path=Name}" Name="txtButtonLabel" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Black"/>
                    </Button>
                </DataTemplate>                
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid Rows="1" Columns="{Binding Views.Count}"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
        <ContentControl Grid.Column="3" Content="{Binding Source={StaticResource ViewsCollection}, Path=CurrentItem}"/>
        <Button Grid.Column="4" Click="Button_Click">dsadsd</Button>
    </StackPanel>
</UserControl>
Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
thrag
  • 1,536
  • 4
  • 20
  • 32

2 Answers2

11

Your button does nothing. Usually your ViewModel would have an ICommand called Select (or something similar) that the Button would be bound against

Command="{Binding Select, ElementName="root"}"

and you'd pass the instance to the ICommand that you'd like to select

CommandParameter="{Binding}"

It would look something like this (c#/XAML like pseudocode):

public class MyModel { public string Name {get;set;} }

public class MyViewModel
{
  public ObservableCollection<MyModel> Models {get;set;}
  public ICommand Select {get;set;}
  /* configure Models and Select etc */
}

<UserControl DataContext="{StaticResource MyViewModelInstance}" x:Name="root">
<ItemsControl ItemsSource="{Binding Models}">
  <ItemsControl.ItemTemplate>
    <ItemsPanelTemplate>
      <Button Text="{Binding Name}" 
      Command="{Binding Select, ElementName="root"}"
      CommandParameter="{Binding}"/>
   </ItemsPanelTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>

The ItemsControl binds to Models, so each MyModel in Models gets a button. The button text is bound to the property Name. The button command is bound to the Select property in the ViewModel. When the button is pressed, it calls the ICommand, sending in the instance of MyModel that the button is bound against.

Please do note that using ViewModels within a UserControl is a code smell. UserControls should appear to users as all other controls--they should have bindable public properties which are bound to the user's ViewModel, not yours. You then bind to the values of these properties within the UserControl. For this example, you would have an ItemsSource property defined on your UserControl, and the ItemsControl would bind to this property rather than a ViewModel directly.

  • Yes, that is what I'm doing (in my app not in the sample) but then I need to set a property like "IsActiveView" in each view model and to keep the other view models in sync by setting IsActiveView=false on each of them. It starts to snow ball. The ListBox in my example does what I need. Is there not a way to make an Items control stay synch with the items it created? The only problem with the list box is it is not horozontal and it is nt making uniform sized areas. Anyway, I appreciate the thought. I'll just wait a it long to see what other ideas come up. Thanks – thrag Dec 21 '09 at 13:35
  • Its a bit hard to grasp what you're trying to do in your app. If I wanted to show details of a particular MyModel I'd have a public MyModel Selected {get;set;} property and I'd bind a ContentControl to it. You can have different DataTemplates per type, so whatever type of object you bind against will be shown using a different DataTemplate... –  Dec 21 '09 at 13:55
  • OK, that works. But, a follow on question; if I have five buttons on screen. Each activates a view bound to a content present (as you suggest). I want to have the button that represents the active view highlighted. Is there a way I can use a Trigger to change the color of the buttons based on the "Selected" view property. So only one button is ever colored (highlighted as active.) It would behave like a tab control but I want need the buttons spaced evenly across the top. (Which makes me think I should see if could chage the tab contol's template to use a uniform Grid – thrag Dec 22 '09 at 04:12
  • Could be. It would probably be much harder than just setting a public property on your ViewModel to indicate what button should be highlighed, then using a Style DataTrigger bound to that public property to turn the highlight on and off... `public enum Highlight { ViewA, ViewB, ViewC, ViewD }` –  Dec 22 '09 at 12:10
1

I think you have to do it manually in code-behind.

XAML

<Button Click="ViewListButton_Click">
    <TextBlock Text="{Binding Path=Name}" Name="txtButtonLabel" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Black"/>
</Button>

Code-behind

private void ViewListButton_Click(object sender, RoutedEventArgs e)
{
    Button button = sender as Button;
    ICollectionView view = CollectionViewSource.GetDefaultView(ViewList.ItemsSource);
    view.MoveCurrentTo(button.DataContext);
}

If you're using the MVVM pattern and/or you don't want to use code-behind, you could bind the button's Command to a command in your ViewModel, as suggested by Will

Hossein Narimani Rad
  • 31,361
  • 18
  • 86
  • 116
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758