Attaching an Event Handler to a Code Generated DataTemplate - xaml

I have a question related to this one: I'm trying to attach an event to my StackPanel but it doesn't appear to connect when using the XamlReader. I can't get the ChildItem_Load method to get called. Does anyone know of a way to do this?
Other than this event the code works fine.
this._listBox.ItemTemplate = (DataTemplate) XamlReader.Load(
#"<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
<Border>
<StackPanel Loaded=""ChildItem_Loaded"">
<TextBlock Text=""{Binding " + this._displayMemberPath + #"}"" />
</StackPanel>
</Border>
</DataTemplate>"

Ok, I figured out a bit of a 'hack' solution, but it works.
Since it looks like the XamlReader doesn't have any knowledge of the local namespace when creating the DataTemplate I extended the StackPanel and "baked-in" the Load event. It's not exactly ideal, but it works:
this._listBox.ItemTemplate = (DataTemplate) XamlReader.Load(
#"<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:foo=""clr-namespace:Foo;assembly=Foo"">
<Border>
<foo:ExtendedStackPanel>
<TextBlock Text=""{Binding " + this._displayMemberPath + #"}"" />
</foo:ExtendedStackPanel>
</Border>
</DataTemplate>"
);
And the extended class:
public class ExtendedStackPanel : StackPanel
{
public ExtendedStackPanel() : base()
{
this.Loaded += new RoutedEventHandler(this.ChildItem_Loaded);
}
private void ChildItem_Loaded(object sender, RoutedEventArgs e)
{
// Logic here...
}
}

Related

UWP Binding: Changing backgrounds in XAML using C#

Suppose I am making a simple UWP application which navigates through several pages. I would like to have a common background for all pages, depending on which background a user has selected from the Settings page.
I have a SettingsPage.xaml with a comboBox (and Grid Background that needs to change):
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ComboBox Name="ColourSelect" SelectionChanged="ComboBox_SelectionChanged">
<ComboBoxItem Name="Red">Red</ComboBoxItem>
<ComboBoxItem Name="Green">Green</ComboBoxItem>
<ComboBoxItem Name="Blue">Blue</ComboBoxItem>
</ComboBox>
</Grid>
Which interfaces with my SettingsPage.xaml.cs file:
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Change background
if (Red.IsSelected) { } // Change to Red.png
else if (Green.IsSelected) { } // Change to Green.png
else if (Blue.IsSelected) { } // Change to Blue.png
}
I have set up my App.xaml to contain a background resource, but I'm not sure how to bind it to the C# in Settings.xaml.cs.
<Application.Resources>
<Style TargetType="Grid" x:Key="CommonBackground">
<Setter Property="Background" Value="{ <!-- Some image. How to bind? --> }"
</Style>
</Application.Resources>
What should I return to bind the user decision to the Application resources?
Thank you in advance!
This requires few changes in different pieces of application. Follow my steps.
In this case I a am creating two Resources. One that will maintain the Settings Combobox Colour Scheme. Second one is BitMapImage in Resource.
So my Application.Resource will look something like below.
<Application.Resources>
<image:BitmapImage x:Key="BackgroundSource" UriSource="ms-appx:///Assets/Red.png" />
<x:String x:Key="BackgroundBrush">Red</x:String>
</Application.Resources>
Make sure you are adding xmlns:image="using:Windows.UI.Xaml.Media.Imaging" in your App.xaml.
Now Create a Static Method inside App.xaml.cs that will be used to update Background to the Page during Run time. It should be something like below.
public static void UpdateBGColors(string Color)
{
switch (Color)
{
case "Red":
Current.Resources["BackgroundSource"] = "ms-appx:///Assets/Red.png";
break;
case "Green":
Current.Resources["BackgroundSource"] = "ms-appx:///Assets/Green.png";
break;
case "Blue":
Current.Resources["BackgroundSource"] = "ms-appx:///Assets/Blue.png";
break;
default:
Current.Resources["BackgroundSource"] = "ms-appx:///Assets/Red.png";
break;
}
}
Now Your combobox_SelectionChanged should look like below.
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox cb = sender as ComboBox;
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
localSettings.Values["BackgroundBrush"] = (cb.SelectedValue as ComboBoxItem).Content;
App.UpdateBGColors((cb.SelectedValue as ComboBoxItem).Content.ToString());
}
Now you need to wire up the Background of each page to the Resource BackgroundSource. So anywhere you want the background to be set based on settings add below lines of code
<Grid>
<Grid.Background>
<ImageBrush ImageSource="{StaticResource BackgroundSource}" />
</Grid.Background>
......
</Grid>
At this point, if you change the setting in setting page and if you navigate back to original page that you came into setting page, The background should be set automatically to whatever you selected in Settings.
But you also want to make sure the same background is loaded when the app is opened next time. To do that in App.xaml.cs, Add below lines in the beginning of OnLaunched Event.
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
if (localSettings.Values["BackgroundBrush"] != null)
{
UpdateBGColors(localSettings.Values["BackgroundBrush"].ToString());
}
Since in settings page, you are saving BackgroundBrush Everytime you change the Combobox Item, Whenever your app is loading, Based on the BackgroundBrush BackgroundSource will be assigned to correct Uri and will be used as Page Backhground.
Full Repo is available Here
Good Luck.
[Update] You can use this, and after save your settings.
SettingsPage.xaml
<Grid>
<Grid.Background>
<ImageBrush x:Name="colorImage" Stretch="UniformToFill"/>
</Grid.Background>
<ComboBox Name="ColourSelect" SelectionChanged="ComboBox_SelectionChanged">
<ComboBoxItem Name="Red">Red</ComboBoxItem>
<ComboBoxItem Name="Green">Green</ComboBoxItem>
<ComboBoxItem Name="Blue">Blue</ComboBoxItem>
</ComboBox>
</Grid>
SettingsPage.xaml.cs
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (Red.IsSelected)
{
ChangeColorImage("ms-appx:///Assets/Red.png");
}
else if (Green.IsSelected)
{
ChangeColorImage("ms-appx:///Assets/Green.png");
}
else if (Blue.IsSelected)
{
ChangeColorImage("ms-appx:///Assets/Blue.png");
}
}
private void ChangeColorImage(string imageUrl)
{
// using Windows.UI.Xaml.Media.Imaging;
BitmapImage imageSource = new BitmapImage(new Uri(imageUrl));
colorImage.ImageSource = imageSource;
}

How to make sure a Popup control match its parent Page when the parent is resized? UWP

I have a Popup which will fill the whole page when opened.
<Grid x:Name="gridRoot" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Content="Open" HorizontalAlignment="Center" Click="{x:Bind viewModel.OpenPopup}" />
<Popup x:Name="popupCorrect" VerticalAlignment="Top" IsOpen="{Binding IsOpen}" IsLightDismissEnabled="False">
<Popup.ChildTransitions>
<TransitionCollection>
<PaneThemeTransition Edge="Left" />
</TransitionCollection>
</Popup.ChildTransitions>
<uc:MyPopup Width="{Binding ElementName=gridRoot, Path=ActualWidth}" Height="{Binding ElementName=gridRoot, Path=ActualHeight}"/>
</Popup>
</Grid>
The Popup is a UserControl
<Grid Background="Red">
<Button Content="Close" HorizontalAlignment="Center" Click="{x:Bind viewModel.ClosePopup}" />
</Grid>
The page
When popup is shown
Close the popup, resize the page, then reopen the popup. Notice that it does not match the new size of container page even though its Width and Height is bound to gridRoot . Do I have to manually set a new Width and Height for the popup? Why can't I achieve this with binding? This issue also appears on mobile during 'OrientationChanged'
Based on Decade Moon comment, this is how to resize the popup to match the parent container as its size changed.
Create a dependency property in the code behind
public double PageWidth
{
get { return (double)GetValue(PageWidthProperty); }
set { SetValue(PageWidthProperty, value); }
}
public static readonly DependencyProperty PageWidthProperty =
DependencyProperty.Register("PageWidth", typeof(double), typeof(GamePage), new PropertyMetadata(0d));
public double PageHeight
{
get { return (double)GetValue(PageHeightProperty); }
set { SetValue(PageHeightProperty, value); }
}
public static readonly DependencyProperty PageHeightProperty =
DependencyProperty.Register("PageHeight", typeof(double), typeof(GamePage), new PropertyMetadata(0d));
Update the value on SizeChanged event
private void GamePage_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Width > 0d && e.NewSize.Height > 0d)
{
PageWidth = e.NewSize.Width;
PageHeight = e.NewSize.Height;
}
}
Then in XAML, just use x:Bind to bind the popup width and height
<Popup x:Name="popupCorrect" VerticalAlignment="Top" IsOpen="{Binding IsPopupCorrectOpen, Mode=TwoWay}" IsLightDismissEnabled="False">
<Popup.ChildTransitions>
<TransitionCollection>
<PaneThemeTransition Edge="Left" />
</TransitionCollection>
</Popup.ChildTransitions>
<uc:PopupCorrect Width="{x:Bind PageWidth, Mode=TwoWay}" Height="{x:Bind PageHeight, Mode=TwoWay}"/>
</Popup>
Pretty straight forward. Just remember not to use the ActualWidth or ActualHeight properties for binding source as they do not raise the PropertyChanged event.
Although it has an ActualWidthProperty backing field, ActualWidth does not raise property change notifications and it should be thought of as a regular CLR property and not a dependency property.
For purposes of ElementName binding, ActualWidth does not post updates when it changes (due to its asynchronous and run-time calculated nature). Do not attempt to use ActualWidth as a binding source for an ElementName binding. If you have a scenario that requires updates based on ActualWidth, use a SizeChanged handler.
#PutraKg have a great way.
But I have two way to solve it.
The first is set the VerticalAlignment="Center" HorizontalAlignment="Center" that can make the popup in the center.
But I think youare not content to put it in the center.
The great way is use the screen position.
You can get the Grid's screen postion and make it to popup.
In open button
private void Button_OnClick(object sender, RoutedEventArgs e)
{
var grid = (UIElement)popupCorrect.Parent; //get grid
var p = grid.TransformToVisual (Window.Current.Content).TransformPoint(new Point(0, 0)); //get point
popupCorrect.HorizontalOffset = p.X;
popupCorrect.VerticalOffset = p.Y;
popupCorrect.IsOpen = !popupCorrect.IsOpen;
}

UWP FlipView prevent user interactions

Yes, i have googled for it, this solution doesn't do the trick:
Disable navigation on FlipView
Because i want to remain changing items with animations, but only programmatically.
I have investigated the FlipView template and found that all interactions/animations etc. are built using the ScrollingHost by name:
<ScrollViewer x:Name="ScrollingHost" AutomationProperties.AccessibilityView="Raw" BringIntoViewOnFocusChange="{TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}" HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" HorizontalSnapPointsType="MandatorySingle" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" IsTabStop="False" IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}" IsHorizontalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsHorizontalScrollChainingEnabled}" IsVerticalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsVerticalScrollChainingEnabled}" IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}" IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}" Padding="{TemplateBinding Padding}" TabNavigation="{TemplateBinding TabNavigation}" VerticalSnapPointsType="MandatorySingle" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" ZoomMode="Disabled">
<ItemsPresenter/>
</ScrollViewer>
So, i have created a custom class MyFlipView and derivered from FlipView, and overrided MouseWheel ( this gives me the needed behavior on desktop ), and deleted navigation buttons from template ( this also limits user interactions ). But the only thing that remains, user is still able to drag the items by pointer ( tablets, phones, maybe even PC with touch screens ). here is my code:
public class MyFlipView : FlipView
{
ScrollViewer scroll;
public MyFlipView()
{
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
scroll = GetTemplateChild("ScrollingHost") as ScrollViewer;
scroll.HorizontalScrollMode = ScrollMode.Disabled;
scroll.VerticalScrollMode = ScrollMode.Disabled;
scroll.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
scroll.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
scroll.IsHorizontalRailEnabled = false;
scroll.IsVerticalRailEnabled = false;
scroll.IsVerticalScrollChainingEnabled = false;
scroll.IsHorizontalScrollChainingEnabled = false;
scroll.IsHoldingEnabled = false;
}
protected override void OnPointerWheelChanged(PointerRoutedEventArgs e)
{
e.Handled = true;
//base.OnPointerWheelChanged(e);
}
}
As you can see i tried do disable all the scrolling in the ScrollingHost, but still it is possible to switch items on touchscreens. How can i disable them also?
Set the FlipView's IsHitTestVisible property to false.
Then set KeyboardNavigation.DirectionalNavigation to none
Which is basically saying "You can't touch me or tab to me"
Thomas Schneiter's answer would still allow it to be reached via tabbing / other buggy methods of navigation.
If you don't need any interaction with the FlipView, an easy but... well kinda ugly solution is to put a (almost) transparent rectangle on top of your FlipView.
<Grid>
<FlipView ... />
<Rectangle Fill="White" Opacity="0.01" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</Grid>
Over a year later and still no good solution for disabling FlipView's. Here's what I ended up doing which seems to do the trick without having to create a new FlipView control.
Edit the ControlTemplate and remove all the navigation buttons. Add a transparent background to you DataTemplate container. Add a PointerWheelChanged event handler to the DataTemplate container and have it set e.Handled = true;. Finally, add a SelectionChanged event handler to the FlipView and have it reset the selected item back to the original.
XAML
<FlipView x:Name="MyFlipView" ItemsSource="{x:Bind Items,Mode=OneWay}" SelectionChanged="FlipView_SelectionChanged">
<FlipView.ItemTemplate>
<DataTemplate>
<Grid Background="Transparent" PointerWheelChanged="Item_PointerWheelChanged">
<!--You Item XAML-->
</Grid>
</DataTemplate>
</FlipView.ItemTemplate>
<FlipView.Template>
<ControlTemplate TargetType="FlipView">
<Grid>
<!--Default ScrollViewer settings omitted, but no changes needed-->
<ScrollViewer>
<ItemsPresenter />
</ScrollViewer>
</Grid>
</ControlTemplate>
</FlipView.Template>
</FlipView>
Code Behind
private bool _isDisabled;
private object _selectedItem;
public ObservableCollection<object> Items { get; } = new ObservableCollection<object>();
//methods for setting Items, _isDisabled, and _selectedItem omitted
private void Item_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
//this prevents the wheel from changing the items
e.Handled = _isDisabled;
}
private void FlipView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//Can't find a way to disable keyboard from changing items so we'll monitor the change and flip it back
if (_isDisabled)
{
this.SetupSelectedItem();
}
}
private void SetupSelectedItem()
{
//do a check here to prevent SelectionChanged events from firing.
if (this.MyFlipView.SelectedItem != _selectedItem)
{
this.MyFlipView.SelectedItem = _selectedItem;
}
}
UPDATE
Ok, TouchScreen was still having some problems so I took another stab at it. There are two levels we need to work on, the FlipViewItem and the FlipView. Unfortunately we have to sub-class these because there aren't any overrides we can do to interrupt the default behavior. I also wanted to use binding to be able to turn the flipping on or off. This only supports Horizontal orientation but can easily be adjusted to support Vertical.
Custom FlipViewItem is needed to control the mouse and keyboard.
public sealed class GalleryFlipViewItem : FlipViewItem
{
public bool IsFlipEnabled { get; set; }
protected override void OnKeyDown(KeyRoutedEventArgs e)
{
e.Handled = !this.IsFlipEnabled;
base.OnKeyDown(e);
}
protected override void OnPointerWheelChanged(PointerRoutedEventArgs e)
{
e.Handled = !this.IsFlipEnabled;
base.OnPointerWheelChanged(e);
}
}
Custom FlipView is needed to control the touch and trickle the setting to the custom FlipViewItem.
public sealed class GalleryFlipView : FlipView
{
public static readonly DependencyProperty IsFlipEnabledProperty =
DependencyProperty.Register("IsFlipEnabled", typeof(bool), typeof(GalleryFlipView), new PropertyMetadata(true, IsFlipEnabledChanged));
private static void IsFlipEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is GalleryFlipView control)
{
if (control.GetTemplateChild("ScrollingHost") is ScrollViewer scrollViewer)
{
scrollViewer.HorizontalScrollMode = control.IsFlipEnabled ? ScrollMode.Auto : ScrollMode.Disabled;
}
if (control.ContainerFromItem(control.SelectedItem) is GalleryFlipViewItem flipViewItem)
{
flipViewItem.IsFlipEnabled = control.IsFlipEnabled;
}
}
}
public bool IsFlipEnabled
{
get { return (bool)GetValue(IsFlipEnabledProperty); }
set { SetValue(IsFlipEnabledProperty, value); }
}
protected override DependencyObject GetContainerForItemOverride()
{
return new GalleryFlipViewItem
{
IsFlipEnabled = this.IsFlipEnabled
};
}
}
Now we need to add the styles for each of these controls. I just copied the default styles for FlipViewItem and FlipView. I made a small adjustment to the FlipView by removing the up/down buttons and added binding to control the left/right button's visibility. To do this, wrap the original button in a grid and then use a converter to convert the bool to a visibility enum.
<Grid HorizontalAlignment="Left" VerticalAlignment="Center" Visibility="{Binding IsFlipEnabled,RelativeSource={RelativeSource TemplatedParent},Converter={StaticResource BooleanToVisibleConverter}}">
<Button x:Name="PreviousButtonHorizontal" Height="36" IsTabStop="False" Template="{StaticResource HorizontalPreviousTemplate}" UseSystemFocusVisuals="False" Width="20" />
</Grid>
<Grid HorizontalAlignment="Right" VerticalAlignment="Center" Visibility="{Binding IsFlipEnabled,RelativeSource={RelativeSource TemplatedParent},Converter={StaticResource BooleanToVisibleConverter}}">
<Button x:Name="NextButtonHorizontal" Height="36" IsTabStop="False" Template="{StaticResource HorizontalNextTemplate}" UseSystemFocusVisuals="False" Width="20" />
</Grid>
Since IsFlipEnabled is a DependencyProperty you can use binding, xaml, or code behind to control it.
<controls:GalleryFlipView IsFlipEnabled="False">
Finally, this seems like the right solution. So frustrating we have to do so much work to simply turn this feature on and off.

Windows Phone 8.1 Toggling the visibility of a TextBlock in a DataTemplate

I'm building a Windows Phone 8.1 Hub Application. One of the hub section contains a ListView that displays a list of articles. I'd like to add a Textblock to this hubsection which displays a message when the articles failed to download. The XAML Code is below:
<HubSection
x:Uid="ArticlesSection"
Header="ARTICLES"
DataContext="{Binding Articles}"
HeaderTemplate="{ThemeResource HubSectionHeaderTemplate}">
<DataTemplate>
<Grid>
<ListView
AutomationProperties.AutomationId="ItemListViewSection3"
AutomationProperties.Name="Items In Group"
SelectionMode="None"
IsItemClickEnabled="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource BannerBackgroundArticleTemplate}"
ItemClick="ItemView_ItemClick"
ContinuumNavigationTransitionInfo.ExitElementContainer="True">
</ListView>
<TextBlock
x:Name="NoArticlesTextBlock"
HorizontalAlignment="Center"
VerticalAlignment="center"
Style="{StaticResource HeaderTextBlockStyle}"
TextWrapping="WrapWholeWords"
TextAlignment="Center"/>
</Grid>
</DataTemplate>
</HubSection>
The problem I'm having is that I can't access the TextBlock from the C# code. Is there an easier way to do this?
The problem I'm having is that I can't access the TextBlock from the C# code.
Yes, since the TextBlock is defined inside a DataTemplate, the TextBlock won't be available until the DataTemplate has been applied. Thus, the x:Name attribute won't automatically generate a variable reference in the InitializeComponent method in your *.g.i.cs file. (Read up on XAML Namescopes for more information).
If you want to access it from your code-behind, there are two ways:
The first way is the simplest: you can get a reference to the TextBlock in the sender argument of the Loaded event handler for that TextBlock.
<TextBlock Loaded="NoArticlesTextBlock_Loaded" />
Then in your code-behind:
private TextBlock NoArticlesTextBlock;
private void NoArticlesTextBlock_Loaded(object sender, RoutedEventArgs e)
{
NoArticlesTextBlock = (TextBlock)sender;
}
The second way is to traverse the visual tree manually to locate the element with the required name. This is more suitable for dynamic layouts, or when you have a lot of controls you want to reference that doing the previous way would be too messy. You can achieve it like this:
<Page Loaded="Page_Loaded" ... />
Then in your code-behind:
static DependencyObject FindChildByName(DependencyObject from, string name)
{
int count = VisualTreeHelper.GetChildrenCount(from);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(from, i);
if (child is FrameworkElement && ((FrameworkElement)child).Name == name)
return child;
var result = FindChildByName(child, name);
if (result != null)
return result;
}
return null;
}
private TextBlock NoArticlesTextBlock;
private void Page_Loaded(object sender, RoutedEventArgs e)
{
// Note: No need to start searching from the root (this), we can just start
// from the relevant HubSection or whatever. Make sure your TextBlock has
// x:Name="NoArticlesTextBlock" attribute in the XAML.
NoArticlesTextBlock = (TextBlock)FindChildByName(this, "NoArticlesTextBlock");
}
Jerry Nixon has a good page on his blog about this.

Code behind works but MVVM doesnt

I have been messing with something that works in the code behind but when I try and bind to a MVVM , nothing displays. First I will show the code behind, then MVVM ( same xaml ). I want to use MVVM and not code behind.
Code Behind (works):
var loadOp = ctx.Load<GateBlox.Web.Models.Structure>(ctx.GetStructuresQuery());
loadOp.Completed += (s, e) => { _treeView.ItemsSource = loadOp.Entities.Where(struc => !struc.StructureParentFK.HasValue); };
XAML
<Grid x:Name="LayoutRoot">
<sdk:TreeView x:Name='_treeView' DataContext='{StaticResource ViewModel}'>
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource='{Binding Children}'>
<TextBlock Text='{Binding StructureName}' />
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
</sdk:TreeView>
</Grid>
MVVM (doesnt bind)
private LoadOperation<Structure> _loadStructures;
private StructureContext _structureContext;
private IEnumerable<Structure> _structures;
public IEnumerable<Structure> Structures
{
get { return this._structures; }
set { this._structures = value; RaisePropertyChanged("Structures"); }
}
public StructuresViewModel()
{
if (!DesignerProperties.IsInDesignTool)
{
_structureContext = new StructureContext();
_loadStructures = _structureContext.Load(_structureContext.GetStructuresQuery().Where (p=> ! p.StructureParentFK.HasValue));
_loadStructures.Completed += new EventHandler(_loadStructures_Completed);
}
}
void _loadStructures_Completed(object sender, EventArgs e)
{
this.Structures = _loadStructures.Entities;
}
Have your checked that you are not getting a binding expression error in the output? You are binding the items source of the data template to a property named Children, but your view model exposes a data source named Structures.
Also, in your working example, you are setting the ItemsSource of the TreeView, but in your MVVM XAML you are setting the ItemsSource of your data template. Is there an inconsistency between what ItemsSource you need to set/bind to?
You might also consider using a collection data source that implements the INotifyCollectionChanged interface (ObservableCollection or expose the binding source as a ICollectionView that uses a PagedCollectionView).
I recommend you take a look at this information about data binding in MVVM, as it provides excellent guidance on setting up data sources in your view models.
You are not setting the ItemsSource for your TreeView. I think your xaml should look something like this:
<Grid x:Name="LayoutRoot">
<sdk:TreeView x:Name='_treeView' DataContext='{StaticResource ViewModel}'
ItemsSource="{Binding Structures}">
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource='{Binding Children}'>
<TextBlock Text='{Binding StructureName}' />
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
</sdk:TreeView>
</Grid>
Hope this helps :)
I almost have it working now. I took a different approach and went with a HeirarchicalDataTemplate. At the moment the data is showing but not correctly: The child1 record is shwoing up as a parent as well.
Parent1(level1)
Parent2(level1)
Child1(level2)
Child1(level1)
<navigation:Page x:Class="GateBlox.Views.Structure"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
d:DesignWidth="640"
d:DesignHeight="480"
Title="Structure Page"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:viewmodel="clr-namespace:GateBlox.ViewModels">
<UserControl.Resources>
<viewmodel:StructuresViewModel x:Key='ViewModel'>
</viewmodel:StructuresViewModel>
</UserControl.Resources>
<Grid x:Name="LayoutRoot"
DataContext='{StaticResource ViewModel}'>
<Grid.Resources>
<sdk:HierarchicalDataTemplate x:Key="ChildTemplate"
ItemsSource="{Binding Path=Parent}">
<TextBlock FontStyle="Italic"
Text="{Binding Path=StructureName}" />
</sdk:HierarchicalDataTemplate>
<sdk:HierarchicalDataTemplate x:Key="NameTemplate"
ItemsSource="{Binding Path=Children}"
ItemTemplate="{StaticResource ChildTemplate}">
<TextBlock Text="{Binding Path=StructureName}"
FontWeight="Bold" />
</sdk:HierarchicalDataTemplate>
</Grid.Resources>
<sdk:TreeView x:Name='treeView'
Width='400'
Height='300'
ItemsSource='{Binding Structures}'
ItemTemplate='{StaticResource NameTemplate}'>
</sdk:TreeView>
</Grid>
using System;
using System.Collections.ObjectModel;
using GateBlox.Web.Models;
using System.ServiceModel.DomainServices.Client;
using GateBlox.Web.Services;
using GateBlox.Helpers;
using System.ComponentModel;
using System.Collections.Generic;
namespace GateBlox.ViewModels
{
public class StructuresViewModel : ViewModelBase
{
private LoadOperation<Structure> _loadStructures;
private StructureContext _structureContext;
private ObservableCollection<Structure> _structures;
public ObservableCollection<Structure> Structures
{
get { return this._structures; }
set { this._structures = value; RaisePropertyChanged("Structures"); }
}
public StructuresViewModel()
{
if (!DesignerProperties.IsInDesignTool)
{
_structureContext = new StructureContext();
_loadStructures = _structureContext.Load(_structureContext.GetStructuresQuery());
_loadStructures.Completed += new EventHandler(_loadStructures_Completed);
}
}
void _loadStructures_Completed(object sender, EventArgs e)
{
this.Structures = IEnumerableConverter.ToObservableCollection(_loadStructures.Entities);
}
}
}