Статическая и динамическая панорама предметов в панораме wp7 mvvm - PullRequest
1 голос
/ 28 июня 2011

Вот моя ситуация.

ViewModelA {  
    ObservableCollection<Items> ItemsA  
    ObservableCollection<ViewModelB> ViewModelBs
}

View A с набором текста данных, установленным в ViewModel. ViewA имеет панораму со списком, панелями, текстовыми блоками и т. Д. С источником элементов для списка, привязанного к ItemsA * 1004.*

Я хочу добавить другие элементы панорамы во время выполнения к элементу управления панорамой с помощью общего шаблона данных (список, текстовый блок ... и т. Д.). Каждый элемент панорамы будет связан во время выполнения с каждым ViewModelB в коллекции ViewModelBs.

Я не против что-то делать для этого, потому что я не строгий пурист mvvm ... но решение могло бы быть элегантным, если бы я мог указать элементы управления и шаблоны данных и сделать эту работу.Я новичок в wpf / xaml и пытаюсь взломать эти технологии, написав приложение wp7 ... используя легкую среду mvvm. В конце концов, я хочу, чтобы мои динамически сгенерированные элементы панорамы / содержимое окна списка запускали команды реле наviewmodel, к которому они привязаны ...

Вот фрагмент кода, который я безуспешно пытался сработать.Надеюсь, что это дает некоторую идею ..

<phone:PhoneApplicationPage.Resources>
    <Style x:Key="PanoramaItemStyle" TargetType="ContentControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ContentControl">
                    <Grid x:Name="ContentGrid">
                        <controls:PanoramaItem x:Name="ItemLocationPanoramaItem" Header="{Binding TagName}">
                            <StackPanel >
                                <ListBox  x:Name="ItemLocatorsList" ItemsSource="{Binding ItemLocators}" Height="496" SelectedItem="{Binding SelectedItemLocation, Mode=TwoWay}" >
                                    <Custom:Interaction.Triggers>
                                        <Custom:EventTrigger EventName="SelectionChanged">
                                            <GalaSoft_MvvmLight_Command:EventToCommand x:Name="SelectionChangedEvent" Command="{Binding RelativeSource={RelativeSource TemplatedParent},Path=DataContext.GoToEditItemLocatorCommand}" PassEventArgsToCommand="True"/>
                                        </Custom:EventTrigger>
                                    </Custom:Interaction.Triggers>
                                    <ListBox.ItemsPanel>
                                        <ItemsPanelTemplate >
                                            <StackPanel Orientation="Vertical"  ScrollViewer.VerticalScrollBarVisibility="Auto" />
                                        </ItemsPanelTemplate>
                                    </ListBox.ItemsPanel>
                                    <ListBox.ItemTemplate>
                                        <DataTemplate>
                                            <StackPanel> 
                                                    <StackPanel Orientation="Horizontal" Margin="0,0,0,17">
                                                        <StackPanel Width="311">
                                                            <TextBlock Text="{Binding Path=Item.Name}" TextWrapping="Wrap" Style="{StaticResource PhoneTextLargeStyle}"/>
                                                            <TextBlock Text="{Binding Path=Location.Description}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                                                        </StackPanel>
                                                    </StackPanel> 
                                            </StackPanel>
                                        </DataTemplate>
                                    </ListBox.ItemTemplate>
                                </ListBox>
                            </StackPanel>
                        </controls:PanoramaItem>
                        <ContentPresenter/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="Foreground" Value="White"/>
    </Style> 
</phone:PhoneApplicationPage.Resources>


Codebehind: private LocationGroupsViewModel viewModel = null;

    public LocationGroups()
    {
        InitializeComponent(); 
        LocationGroupsPanaroma.DefaultItem = LocationGroupsPanaroma.Items[0];
         viewModel = this.DataContext as LocationGroupsViewModel;
         CreateDynamicPanaromaItems();
    }


    private void CreateDynamicPanaromaItems()
    {
        foreach (Model.LocationGroup group in viewModel.LocationGroups)
        {
            if (group.TotalItems > 0)
            {
                PanoramaItem pi = new PanoramaItem();
                pi.Header = group.Name;
                pi.Orientation = System.Windows.Controls.Orientation.Horizontal;
                ItemLocationListViewModel itemLocationViewModel = viewModel[group.LocationGroupId];
                pi.DataContext = itemLocationViewModel;
                pi.Style = Resources["PanoramaItemStyle"] as Style;
                LocationGroupsPanaroma.Items.Add(pi);

            }
        }

    }

Изменить

ViewModelA имеет

Items collection

Collection of ViewModelBs

контекст данных панаромы, установленный в viewmodelA

panaroma item - Statitically created in xaml to some Items collection in ViewModelA

    This pan item has a list box



panaroma items --- to be bound to collection of viewmodelbs

    These pan items should each have a listbox which is selectable
                  and  bound to some collection in View Model B and fires commands on    selection         changed to viewModelB. Currently using the galasoft eventtocommand to hook the selection changed on the
    list box to a relay command. The problem is that this eventtommand should have the viewmodel as its data context and the not the collection (bound to the listbox) within viewmodel. 

1 Ответ

3 голосов
/ 01 июля 2011

ОК, наконец-то пришло время ответить на вопрос. Предложенное решение не требует никакого кода позади и опирается только на концепции MVVM и привязку данных.

Концептуально элемент управления Panorama является ItemPresenter (он наследует от ItemsPresenter), т. Е. Вы можете связать ItemsSource со списком, который содержит элементы, которые представляют ваш PanoramaItems.

Чтобы отобразить PanoramaItem, вам нужно будет предоставить шаблоны для Panorama.HeaderTemplate и Panorama.ItemTemplate. DataContext внутри шаблонов - это ViewModel, который представляет ваш PanoramaItem. Если этот ViewModel содержит список предметов, теперь вы можете использовать его для генерации ListBoxes, который вы искали.

А вот и образец ...

ViewModelLocator.cs

using GalaSoft.MvvmLight;

namespace WP7Test.ViewModel
{
    public class ViewModelLocator
    {
        private static MainViewModel _main;

        public ViewModelLocator()
        {
    if (ViewModelBase.IsInDesignModeStatic) {
        // Create design time services and viewmodels
    } else {
        // Create run time services and view models
    }
            _main = new MainViewModel();
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
            "CA1822:MarkMembersAsStatic",
            Justification = "This non-static member is needed for data binding purposes.")]
        public MainViewModel Main
        {
            get
            {
                return _main;
            }
        }
    }
}

MainViewModel.cs

public class MainViewModel : ViewModelBase
{
    public MainViewModel()
    {
        this.Items = new ObservableCollection<ItemViewModel>();

        if (IsInDesignMode) {
            // Code runs in Blend --> create design time data.
        } else {
            // Code runs "for real"
        }
        this.LoadData();
    }

    #region [Items]

    public const string ItemsPropertyName = "Items";

    private ObservableCollection<ItemViewModel> _items = default(ObservableCollection<ItemViewModel>);

    public ObservableCollection<ItemViewModel> Items {
        get {
            return _items;
        }
        private set {
            if (_items == value) {
                return;
            }

            var oldValue = _items;
            _items = value;

            RaisePropertyChanged(ItemsPropertyName);
        }
    }

    #endregion

    private void LoadData() {
        this.Items.Add(new ItemViewModel() { LineOne = "runtime one", LineTwo = "Maecenas praesent accumsan bibendum", LineThree = "Facilisi faucibus habitant inceptos interdum lobortis nascetur pharetra placerat pulvinar sagittis senectus sociosqu" });
        this.Items.Add(new ItemViewModel() { LineOne = "runtime two", LineTwo = "Dictumst eleifend facilisi faucibus", LineThree = "Suscipit torquent ultrices vehicula volutpat maecenas praesent accumsan bibendum dictumst eleifend facilisi faucibus" });
        this.Items.Add(new ItemViewModel() { LineOne = "runtime three", LineTwo = "Habitant inceptos interdum lobortis", LineThree = "Habitant inceptos interdum lobortis nascetur pharetra placerat pulvinar sagittis senectus sociosqu suscipit torquent" });

        foreach (var item in Items) {
            for (int i = 0; i < 5; ++i)
                item.Items.Add(new ItemViewModel() { LineOne = "Item " + i, LineTwo = "Maecenas praesent accumsan bibendum" });
        }
    }
}

ItemViewModel.cs

public class ItemViewModel : ViewModelBase
{
    public ItemViewModel() {
        this.Items = new ObservableCollection<ItemViewModel>();

        if (IsInDesignMode) {
            // Code runs in Blend --> create design time data.
        } else {
            // Code runs "for real": Connect to service, etc...
        }
    }

    public override void Cleanup() {
        // Clean own resources if needed

        base.Cleanup();
    }

    #region [LineOne]

    public const string LineOnePropertyName = "LineOne";

    private string _lineOne = default(string);

    public string LineOne {
        get {
            return _lineOne;
        }

        set {
            if (_lineOne == value) {
                return;
            }

            var oldValue = _lineOne;
            _lineOne = value;
            RaisePropertyChanged(LineOnePropertyName);
        }
    }

    #endregion

    #region [LineTwo]

    public const string LineTwoPropertyName = "LineTwo";

    private string _lineTwo = default(string);

    public string LineTwo {
        get {
            return _lineTwo;
        }

        set {
            if (_lineTwo == value) {
                return;
            }

            var oldValue = _lineTwo;
            _lineTwo = value;

            RaisePropertyChanged(LineTwoPropertyName);
        }
    }

    #endregion

    #region [LineThree]

    public const string LineThreePropertyName = "LineThree";

    private string _lineThree = default(string);

    public string LineThree {
        get {
            return _lineThree;
        }

        set {
            if (_lineThree == value) {
                return;
            }

            var oldValue = _lineThree;
            _lineThree = value;
            RaisePropertyChanged(LineThreePropertyName);
        }
    }

    #endregion

    #region [Items]

    public const string ItemsPropertyName = "Items";

    private ObservableCollection<ItemViewModel> _items = default(ObservableCollection<ItemViewModel>);

    public ObservableCollection<ItemViewModel> Items {
        get {
            return _items;
        }
        private set {
            if (_items == value) {
                return;
            }

            var oldValue = _items;
            _items = value;
            RaisePropertyChanged(ItemsPropertyName);
        }
    }

    #endregion
}

MainPage.xaml

<phone:PhoneApplicationPage 
    x:Class="WP7Test.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800" 
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait"  Orientation="Portrait"
    shell:SystemTray.IsVisible="False">

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent" DataContext="{Binding Main, Source={StaticResource Locator}}">
        <controls:Panorama Title="my application" ItemsSource="{Binding Items}">
            <controls:Panorama.Background>
                <ImageBrush ImageSource="PanoramaBackground.png"/>
            </controls:Panorama.Background>
            <controls:Panorama.HeaderTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding LineOne}"/>
                </DataTemplate>
            </controls:Panorama.HeaderTemplate>
            <controls:Panorama.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <Border BorderThickness="0,0,0,1" BorderBrush="White">
                            <TextBlock Text="{Binding LineTwo}" FontSize="28" TextWrapping="Wrap"/>
                        </Border>
                        <Border BorderThickness="0,0,0,1" Margin="0,20" BorderBrush="White">
                            <TextBlock Text="{Binding LineThree}" TextWrapping="Wrap"/>
                        </Border>
                        <ListBox ItemsSource="{Binding Items}">
                            <ListBox.ItemTemplate>
                                <DataTemplate>
                                    <StackPanel>
                                        <TextBlock Text="{Binding LineOne}" FontSize="24"/>
                                        <TextBlock Text="{Binding LineTwo}" FontSize="18" Margin="24,0,0,5"/>
                                    </StackPanel>
                                </DataTemplate>
                            </ListBox.ItemTemplate>
                        </ListBox>
                    </StackPanel>
                </DataTemplate>
            </controls:Panorama.ItemTemplate>
        </controls:Panorama>
    </Grid>
    <!--Panorama-based applications should not show an ApplicationBar-->
</phone:PhoneApplicationPage>

Редактировать - добавить дополнительную первую панель

Наконец-то я понимаю, чего ты пытаешься достичь! Тем не менее, вам все еще не нужен код для этого! Вам просто нужен шаблон ... для этого Blend поможет вам, поскольку он позволяет вам извлечь шаблон для существующего элемента управления ... хорошо, вот изменения.

Сначала я добавил новое свойство в MainViewModel, чтобы показать некоторые данные:

#region [MainPageProperty]

public const string MainPagePropertyPropertyName = "MainPageProperty";
private string _mainPageProperty = "Facilisi faucibus habitant inceptos interdum lobortis nascetur pharetra placerat pulvinar sagittis senectus sociosqu";

public string MainPageProperty {
    get {
        return _mainPageProperty;
    }
    set {
        if (_mainPageProperty == value) {
            return;
        }

        _mainPageProperty = value;
        RaisePropertyChanged(MainPagePropertyPropertyName);
    }
}

#endregion

Затем я использовал Blend, чтобы получить шаблон для элемента управления Panorama и вставил его в элемент controls:Panorama.

<controls:Panorama.Template>
    <ControlTemplate TargetType="controls:Panorama">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <controlsPrimitives:PanningBackgroundLayer x:Name="BackgroundLayer" HorizontalAlignment="Left" Grid.RowSpan="2">
                <Border x:Name="background" Background="{TemplateBinding Background}" CacheMode="BitmapCache"/>
            </controlsPrimitives:PanningBackgroundLayer>
            <controlsPrimitives:PanningTitleLayer x:Name="TitleLayer" CacheMode="BitmapCache" ContentTemplate="{TemplateBinding TitleTemplate}" Content="{TemplateBinding Title}" FontSize="187" FontFamily="{StaticResource PhoneFontFamilyLight}" HorizontalAlignment="Left" Margin="10,-76,0,9" Grid.Row="0"/>
            <controlsPrimitives:PanningLayer x:Name="ItemsLayer" HorizontalAlignment="Left" Grid.Row="1">
                <StackPanel Orientation="Horizontal">
                    <controls:PanoramaItem Header="Main panel" Width="432">
                        <TextBlock Text="{Binding ElementName=LayoutRoot, Path=DataContext.MainPageProperty}" TextWrapping="Wrap"/>
                    </controls:PanoramaItem>
                    <ItemsPresenter x:Name="items"/>
                </StackPanel>
            </controlsPrimitives:PanningLayer>
        </Grid>
    </ControlTemplate>
</controls:Panorama.Template>

Здесь есть два трюка. Сначала я вставил StacPanel, чтобы под controlPrimitives:PanningLayer было более одного элемента с именем ItemsPanel. В это StackPanel я переместил ItemsPresenter и добавил еще PanoramaItem. Однако важно установить свойство Width для PanoramaItem, так как в противном случае панель будет расширена до нужной комнаты.

Другая хитрость заключается в том, что для получения доступа к DataContext мне пришлось использовать ElementName в Связывании.

Надеюсь, это показывает мощь MVVM и шаблонов!

...