Frage

I'm trying to use Caliburn.Micro to bind a view model of a nested ListBox but I'm stuck. I have a list workspace that is divided in two sections a list of groups containtaining items in a different part of that workspace I want to show the details of either a group or an item in a group depending on what is the SelectedItem. I'm new to Caliburn.Micro and looked at the documentation and samples but don't know how to connect the dots. Specifically I'm trying to model this after the Caliburn.Micro.HelloScreens sample. The code I have so far:

The ViewModel:

public class AnalyzerGroupWorkspaceViewModel : Conductor<AnalyzerGroupWorkspaceViewModel>, IWorkspace
{
    private Selected selected = Selected.AnalyzerGroup;

    private const string name = "Analyzers";

    public AnalyzerGroupWorkspaceViewModel(
        IMappingEngine fromMapper,
        IRepository<Model.AnalyzerGroup> analyzerGroups)
    {
        AnalyzerGroups = new ObservableCollection<IAnalyzerGroupViewModel>(analyzerGroups.GetAll().Select(fromMapper.Map<Model.AnalyzerGroup,AnalyzerGroupViewModel>));
    }

    public ObservableCollection<IAnalyzerGroupViewModel> AnalyzerGroups { get; private set; }

    public string Name { get { return name; } }

    public Selected Selected
    {
        get { return selected; }
        set
        {
            if (value == selected) return;
            selected = value;
            NotifyOfPropertyChange(() => Selected);
        }
    }

    private IConductor Conductor { get { return (IConductor) Parent; } }

    public void Show()
    {
        var haveActive = Parent as IHaveActiveItem;
        if (haveActive != null && haveActive.ActiveItem == this)
        {
            DisplayName = name;
            Selected = Selected.AnalyzerGroup;
        }
        else
        {
            Conductor.ActivateItem(this);
        }
    }
}

The view:

<UserControl x:Class="Philips.HHDx.SSW.AnalyzerGroup.AnalyzerGroupWorkspaceView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:cal="http://www.caliburnproject.org">
    <DockPanel>
        <GroupBox Header="AnalyzerGroups" DockPanel.Dock="Top">
            <ListBox x:Name="AnalyzerGroups">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Vertical">
                            <TextBlock Text="{Binding Name}" />
                            <ListBox x:Name="Analyzers">
                                <ListBox.ItemTemplate>
                                    <DataTemplate>
                                        <TextBlock Text="{Binding Id }"></TextBlock>
                                    </DataTemplate>
                                </ListBox.ItemTemplate>
                            </ListBox>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </GroupBox>
    <GroupBox Header="Details">
        <ContentControl cal:View.Context="{Binding Selected, Mode=TwoWay}"
                        cal:View.Model="{Binding}"
                        VerticalContentAlignment="Stretch"
                        HorizontalContentAlignment="Stretch"/>
    </GroupBox>
    </DockPanel>
</UserControl>

Next to that I have two UserControls that display the detailsof a group or item. My specific question is how can I use the SelectedItem property of the two ListBoxes to modify the Selected property to switch between displaying the AnalyzerGroup details and the Analyzer details?

War es hilfreich?

Lösung

I've found the solution to the above described problem the solution consists of four parts:

  • Add a IsSelected property (that notifies changes) to both 'child' ViewModels
  • Bind the IsSelected property of the ListBox.ItemContainerStyle to the IsSelected property of the respective ViewModels
  • Attach a Caliburn.Micro Message to the 'outer' ListBox and use the $eventArgs argument
  • In the ViewModel bound to the entire UserControl implement the method corresponding to the Message and use the AddedItems property of the eventArgs to set the SelectedViewModel property setting the IsSelected property of the previous SelectedViewModel to false

The code then becomes:

The ViewModel:

public class AnalyzerGroupWorkspaceViewModel : PropertyChangedBase, IAnalyzerGroupWorkspaceViewModel
{
    private IAnalyzerViewModel selectedViewModel;

    private const string WorkspaceName = "Analyzers";

    public AnalyzerGroupWorkspaceViewModel(
        IMappingEngine fromMapper,
        IRepository<Model.AnalyzerGroup> analyzerGroups)
    {
        AnalyzerGroups = new ObservableCollection<IAnalyzerGroupViewModel>(
            analyzerGroups.GetAll().Select(
                fromMapper.Map<Model.AnalyzerGroup, AnalyzerGroupViewModel>));
    }

    public void SelectionChanged(object eventArgs)
    {
        var typedEventArgs = eventArgs as SelectionChangedEventArgs;
        if (typedEventArgs != null)
        {
            if (typedEventArgs.AddedItems.Count > 0)
            {
                var item = typedEventArgs.AddedItems[0];
                var itemAsGroup = item as IAnalyzerViewModel;
                if (itemAsGroup != null)
                {
                    SelectedViewModel = itemAsGroup;
                }
            }
        }
    }

    public ObservableCollection<IAnalyzerGroupViewModel> AnalyzerGroups { get; private set; }

    public string Name { get { return WorkspaceName; } }

    public IAnalyzerViewModel SelectedViewModel
    {
        get { return selectedViewModel; }
        set
        {
            if (Equals(value, selectedViewModel))
            {
                return;
            }
            if (SelectedViewModel != null)
            {
                SelectedViewModel.IsSelected = false;
            }
            selectedViewModel = value;
            NotifyOfPropertyChange(() => SelectedViewModel);
        }
    }
}

The View:

<UserControl x:Class="Philips.HHDx.SSW.AnalyzerGroup.AnalyzerGroupWorkspaceView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:cal="http://www.caliburnproject.org">
    <DockPanel>
        <GroupBox Header="AnalyzerGroups" DockPanel.Dock="Top">
            <ListBox SelectionMode="Single"
                     x:Name="AnalyzerGroups"
                     cal:Message.Attach="[Event SelectionChanged] = [Action SelectionChanged($eventArgs)]">
                <ListBox.ItemContainerStyle>
                    <Style TargetType="{x:Type ListBoxItem}">
                        <Setter Property="IsSelected" Value="{Binding IsSelected}"/>
                    </Style>
                </ListBox.ItemContainerStyle>
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Horizontal" />
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Border BorderThickness="2" BorderBrush="DarkGray">
                            <StackPanel Orientation="Vertical" Margin="10">
                                <TextBlock Text="{Binding Name}" />
                                <ListBox SelectionMode="Single" ItemsSource="{Binding Analyzers}" >
                                    <ListBox.ItemContainerStyle>
                                        <Style TargetType="{x:Type ListBoxItem}">
                                            <Setter Property="IsSelected" Value="{Binding IsSelected}"/>
                                        </Style>
                                    </ListBox.ItemContainerStyle>
                                    <ListBox.ItemsPanel>
                                        <ItemsPanelTemplate>
                                            <StackPanel Orientation="Horizontal"></StackPanel>
                                        </ItemsPanelTemplate>
                                    </ListBox.ItemsPanel>
                                    <ListBox.ItemTemplate>
                                        <DataTemplate>
                                            <Border BorderThickness="1" BorderBrush="DarkGray">
                                                <StackPanel>
                                                    <TextBlock Text="{Binding Name }" Margin="10" />
                                                </StackPanel>
                                            </Border>
                                        </DataTemplate>
                                    </ListBox.ItemTemplate>
                                </ListBox>
                            </StackPanel>
                        </Border>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </GroupBox>
        <GroupBox Header="Details">
            <ContentControl cal:View.Model="{Binding SelectedViewModel}" />
        </GroupBox>
    </DockPanel>
</UserControl>

Andere Tipps

The answer to your specific question is yes, you can.

On the ViewModel of your UserControl. You create a property that is a ViewModel of either of the two details.

public interface IAnalyzerViewModel
{

}

Next, create two ViewModels for the Views of your Analyzer and AnalyzerGroup views.

public class AnalyzerGroupViewModel : IAnalyzerViewModel 
{

}

public class AnalyzerViewModel : IAnalyzerViewModel
{

}

Next, create a property in your UserControl's ViewModel that implements INPC or PropertyChangedBase of Caliburn Micro.

public class MainViewModel :
{
  private IAnalyzerViewModel _analyzerViewModel;
  public IAnalyzerViewModel SelectedViewModel { get { return _analyzerViewModel; } set { _analyzerViewModel = value; OnPropertyChanged(() => SelectedViewModel); }

  //Hook up the selected item changed event of your listbox and set the appropriate ViewModel to show, so if you either want to show the AnalyzerGroup or AnalyzerView.
}

And lastly, just update your MainView to

<ContentControl x:Name="SelectedViewModel"
                VerticalContentAlignment="Stretch"
                HorizontalContentAlignment="Stretch"/>

Caliburn will hook up the appropriate bindings and stuff and will pull the View for the associated ViewModel, and also the Name convention part will automatically map it to any public property of its datacontext as long as the names match.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top