全景 wp7 mvvm 中的静态和动态全景项目 [英] Static and dynamic panorama items in a panorama wp7 mvvm

查看:20
本文介绍了全景 wp7 mvvm 中的静态和动态全景项目的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我的情况.

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

将数据上下文设置为 ViewModel A 的视图 AViewA 有一个包含列表框、面板、文本块等的全景图,列表框的项目源绑定到 ItemsA

View A with the datacontext set to ViewModel A ViewA has a panorama with a listbox, panels, textblocks etc with the items source for the listbox bound to ItemsA

我想在运行时将其他全景项目添加到具有通用数据模板(列表框、文本块...等)的全景控件中.每个全景项目将在运行时绑定到 ViewModelBs 集合中的每个 ViewModelB.

I want to add other panorama items at runtime to the panorama control with a commond data template (listbox, textblock...etc).. Each panorama item will be bound at run time to each ViewModelB in the ViewModelBs collection.

我不反对为此做一些代码隐藏的事情,因为我不是一个严格的 mvvm 纯粹主义者..但如果我可以指定控件和数据模板并使其工作,解决方案可能会很优雅.我对 wpf/xaml 有点陌生,并试图通过编写 wp7 应用程序来使用这些技术.他们被绑定到的视图模型......

I am not oppose to doing some codebehind stuff for this as I am not a strict mvvm purist.. but the solution could be elegant if I could specify control and data templates and make this work. I am kind of new to wpf/xaml and trying to break in to those technologies by writing a wp7 app.. using the mvvm light framework.. Eventually, i want my dynamically generated panorama items/list box content fire off relay commands on the viewmodel they are being bound to...

这是我尝试不成功的一些代码片段.希望它提供一些想法..

Here is some code snippet that i tried unsuccessfully to work. Hope it provides some idea..

<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>


代码隐藏:私有 LocationGroupsViewModel viewModel = null;

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);

            }
        }

    }

编辑

ViewModel A 有

ViewModel A has

Items collection

Collection of ViewModelBs

全景数据上下文设置为 viewmodelA

panaroma data context set to 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. 

推荐答案

好的,终于有时间回答问题了.所提出的解决方案不需要任何背后的代码,仅依赖于 MVVM 概念和数据绑定.

OK, finally got time to answer the question. The proposed solution does not require any code behind and only relys on MVVM concepts and data binding.

从概念上讲,Panorama 控件是一个 ItemPresenter(它继承自 ItemsPresenter),即您可以将 ItemsSource 绑定到包含项目的列表代表您的 PanoramaItems.

Conceptionally the Panorama control is an ItemPresenter (it inherits from ItemsPresenter), i.e. you can bind the ItemsSource to a list that contains items that repesent your PanoramaItems.

要呈现您的 PanoramaItem,您必须为 Panorama.HeaderTemplatePanorama.ItemTemplate 提供模板.模板内的 DataContext 是表示您的 PanoramaItemViewModel.如果此 ViewModel 包含一个项目列表,您现在可以使用它来生成您正在寻找的 ListBoxes.

To render your PanoramaItem you will have to provide templates for Panorama.HeaderTemplate and Panorama.ItemTemplate. The DataContext inside the templates is the ViewModel that represents your PanoramaItem. If this ViewModel contains an item list you now can use it to generate the ListBoxes you were looking for.

这是示例...

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 确实可以帮助您,因为它可以让您为现有控件提取模板...好的,这是更改.

Finally I understand what you trying to achive! However, you still need no code behind to do it! You just need a template ... for this Blend does help you as it lets you extract a template for a exiting control ... ok, here are the changes.

首先,我向 MainViewModel 添加了一个新属性以显示一些数据:

First I added a new property to the MainViewModel to show some data:

#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 元素中.

Then I used Blend to get the template for the Panorama control and inserted it into the controls:Panorama element.

<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.但是,重要的一件事是设置 PanoramaItemWidth 属性,否则面板将延伸到所需的房间.

There are two tricks here, first I inserted a StacPanel to have allow for more than one element underneath the controlPrimitives:PanningLayer with the name ItemsPanel. Into this StackPanel I moved the ItemsPresenter and added another PanoramaItem. One thing that is important, though, is to set the Width property of the PanoramaItem, as otherwise the panel will extend to the room that is needed.

另一个技巧是,为了访问DataContext,我必须在绑定中使用ElementName.

The other trick is that in order to get access to the DataContext I had to use the ElementName in the Binding.

希望这能展示 MVVM 和模板的强大功能!

这篇关于全景 wp7 mvvm 中的静态和动态全景项目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆