这是我的情况。
ViewModelA {
ObservableCollection<Items> ItemsA
ObservableCollection<ViewModelB> ViewModelBs
}
使用设置为ViewModel A的datacontext查看A. ViewA有一个包含列表框,面板,文本块等的全景图,其中列表框的项目源绑定到ItemsA
我想在运行时使用commond数据模板(列表框,文本块等)将其他全景项目添加到全景控件中。每个全景项目将在运行时绑定到ViewModelBs集合中的每个ViewModelB。
我并不反对为此做一些代码隐藏的东西,因为我不是一个严格的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>
代码隐藏: 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
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.
答案 0 :(得分:3)
好的,终于有时间回答这个问题了。建议的解决方案不需要任何代码,只依赖于MVVM概念和数据绑定。
概念上Panorama
控件是ItemPresenter(它继承自ItemsPresenter
),即您可以将ItemsSource
绑定到包含重复PanoramaItems
项的列表。
要呈现PanoramaItem
,您必须提供Panorama.HeaderTemplate
和Panorama.ItemTemplate
的模板。模板中的DataContext
是代表ViewModel
的{{1}}。如果此PanoramaItem
包含项目列表,您现在可以使用它来生成您正在寻找的ViewModel
。
以下是样本......
<强> ViewModelLocator.cs 强>
ListBoxes
<强> MainViewModel.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;
}
}
}
}
<强> ItemViewModel.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" });
}
}
}
<强> MainPage.xaml中强>
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
}
修改 - 添加额外的第一个面板
最后,我明白你想要实现的目标!但是,您仍然无需执行任何代码!你只需要一个模板......对于这个Blend确实可以帮到你,因为它可以让你为一个现有的控件提取模板......好的,这里有更改。
首先,我向MainViewModel添加了一个新属性,以显示一些数据:
<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获取Panorama控件的模板并将其插入#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
元素。
controls:Panorama
这里有两个技巧,首先我插入了一个StacPanel,允许<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>
下面有多个名为controlPrimitives:PanningLayer
的元素。在此ItemsPanel
中,我移动了StackPanel
并添加了另一个ItemsPresenter
。但有一件重要的事情是设置PanoramaItem
的{{1}}属性,否则面板将扩展到所需的房间。
另一个技巧是,为了访问Width
我必须在绑定中使用PanoramaItem
。
希望这能展示MVVM和模板的力量!