【问题标题】:Static and dynamic panorama items in a panorama wp7 mvvm全景 wp7 mvvm 中的静态和动态全景项目
【发布时间】:2011-06-28 01:23:41
【问题描述】:

这是我的情况。

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

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

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

我不反对为此做一些代码隐藏,因为我不是严格的 mvvm 纯粹主义者。 但如果我可以指定控制和数据模板并使其工作,该解决方案可能会很优雅。我对 wpf/xaml 有点陌生,并试图通过编写 wp7 应用程序来闯入这些技术。使用 mvvm light 框架。最终,我希望我的动态生成的全景项目/列表框内容触发中继命令他们被绑定到的视图模型......

这里是一些我尝试工作失败的代码 sn-p。希望它提供一些想法..

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

    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 有

Items collection

Collection of ViewModelBs

panaroma 数据上下文设置为 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. 

【问题讨论】:

  • 我可以通过使用样式来使其工作,但命令事件似乎不再在样式中工作。看来我需要将绑定中的数据上下文更改为我的父视图模型..尝试了一些事情但无法让它工作需要这个来触发 >
  • 请注意,堆栈溢出是一个帮助您自己找到解决方案的网站,它可以为您指明正确的方向。它不是一个解决具体编程任务的站点。 IE。它是学习的工具!您已经发布了一个几乎相同的问题,并且通常 - 如果答案不太适合您的问题 - 您编辑您的原始帖子,清楚地标记编辑。帖子中的信息越多,您就越有可能得到合适的答案。关于这个主题,我将看看如何使用模板解决问题 - 但我
  • @obalix 我不期待具体的解决方案。我理解目的。对stackoverflow有点陌生,不知道..我可以编辑原始帖子.... :-)

标签: windows-phone-7 mvvm mvvm-light


【解决方案1】:

好的,终于有时间回答这个问题了。建议的解决方案不需要任何代码,仅依赖于 MVVM 概念和数据绑定。

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

要呈现您的PanoramaItem,您必须为Panorama.HeaderTemplatePanorama.ItemTemplate 提供模板。模板中的DataContext 是代表您的PanoramaItemViewModel。如果这个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。不过,重要的一件事是设置PanoramaItemWidth 属性,否则面板将扩展到所需的房间。

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

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

【讨论】:

  • 问题是我想要一个 panaroma 项目从 ViewModelA 中的 ItemsA 获取数据,而对于其他 panaroma 项目,我将 datacontext 设置为 viewmodelb。您建议的解决方案仅在 panaroma 绑定到单个视图模型时才有效。我想让一个平移项绑定(静态绑定到 viewmodelA)和其他平移项动态绑定到 viewmodelBs 的 observablecollection。我使用了一个样式和控制模板,并在后面加上了一些代码,我可以让它工作。问题是我的 galasoft eventtocommand 在控制模板中不起作用。
  • 我用我的新代码编辑了主帖。问题是我希望将数据上下文更改为以下代码的父级。我尝试了几件事,但不起作用。
  • 查看编辑,这会在构建全景物品列表之前添加另一个全景物品。
  • 你能发布 xaml 吗?我正在通过 wp7 应用程序深入研究 xaml。我尝试了几种方法来获取 galasoft 事件的父上下文以在 pan 项的列表框中进行命令。元素名称绑定似乎不起作用
  • ViewModel A 有 Items 集合 Collection of ViewModelBs panaroma 数据上下文设置为 viewmodelA panaroma 项 - 在 xaml 中静态创建到 ViewModelA 中的某些 Items 集合 此平移项有一个列表框 panaroma 项目 --- 要绑定到 viewmodelbs 的集合这些平移项目应该每个都有一个列表框,该列表框是可选择的,并且在选择更改为 viewModelB 时触发命令。目前他们正在使用 galasoft eventtocommand 将列表框中更改的选择挂钩到中继命令
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多