这是我的情况。
ViewModelA {
ObservableCollection<Items> ItemsA
ObservableCollection<ViewModelB> ViewModelBs
}
将数据上下文设置为ViewModel A的View A
ViewA的全景图具有一个列表框,面板,文本块等,并且列表框的项源绑定(bind)到ItemsA
我想在运行时使用通用数据模板(列表框,文本块等)将其他全景项目添加到全景控件。.每个全景项目将在运行时绑定(bind)到ViewModelBs集合中的每个ViewModelB。
我不反对为此做一些代码隐藏的事情,因为我不是严格的mvvm纯粹主义者。
但是,如果我可以指定控件和数据模板并使其正常工作,则解决方案可能很优雅。我是wpf/xaml的新手,并试图通过编写wp7应用程序来突破这些技术。使用mvvm light框架。最终,我希望动态生成的全景图项目/列表框内容在他们将要绑定(bind)的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>
代码背后:
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有
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.
最佳答案
好,终于有时间回答问题了。提出的解决方案不需要任何代码,仅依赖MVVM概念和数据绑定(bind)。
从概念上讲Panorama
控件是ItemPresenter(它继承自ItemsPresenter
),即,您可以将ItemsSource
绑定(bind)到包含与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
,我不得不在Binding中使用ElementName
。希望这显示了MVVM和模板的功能!
关于windows-phone-7 - 全景图wp7 mvvm中的静态和动态全景图项目,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/6500650/