Make attached properties work inside DataTemplate - wpf

I got an ItemsControl which uses a Canvas as ItemsPanel and its items are rendered to different WPF shapes depending on the bound type, basically like this:
<ItemsControl ItemsSource="{Binding PreviewShapes}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:UiPreviewLineViewModel}">
<Line X1="{Binding Start.X}" Y1="{Binding Start.Y}"
X2="{Binding End.X}" Y2="{Binding End.Y}"
StrokeThickness="0.75" Stroke="{Binding Brush}" x:Name="Line" ToolTip="{Binding Text}">
</Line>
</DataTemplate>
<DataTemplate DataType="{x:Type local:UiPreviewEllipsisViewModel}">
<Ellipse Canvas.Left="{Binding UpperLeft.X" Canvas.Top="{Binding UpperLeft.Y}"
Width="{Binding Width}" Height="{Binding Height}"
StrokeThickness="0.75" Stroke="{Binding Brush}" x:Name="Ellipse" ToolTip="{Binding Text}">
</Ellipse>
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="SketchCanvas" ClipToBounds="False">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
So I basically add objects to PreviewShapes of the viewmodel and depending on the type they are rendered to WPF Lines or Ellipses. That basically works but the attached properties Canvas.Left and Canvas.Top are ignored, even when using static values.
Also VS or ReSharper notifies me that the attached property has no effect in the current context.
How can I position the Ellipse on the canvas without using the attached properties? Or what other solution would be appropiate?

Unfortunately nobody felt like posting an answer.
First, Clemens links are helpful. The items will be inside a ContentPresenter which is the reason why setting Canvas.Left/Top on the Ellipsis does not work.
Solution 1
By adding a style to the item container the bindings for the position can be set there:
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding UpperLeft.X}" />
<Setter Property="Canvas.Top" Value="{Binding UpperLeft.Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
This works but the DataTemplate placing <Line> will produce binding warnings because that view model does not have a property UpperLeft. Nevertheless it works for the ellipsis and the lines are placed by their X1, Y1, X2 and Y2 values.
Solution 2
If you would like to use a more fine grained control approach you can set the attached Canvas properties to the ContentPresenter by proxing them with a custom behaviour / attached property. Let's name it CanvasPointProxyBehavior, you could use it for the Ellipse like this:
<DataTemplate DataType="{x:Type local:UiPreviewEllipsisViewModel}">
<Ellipse behaviors:CanvasPointProxyBehavior.Point="{Binding UpperLeft}"
Width="{Binding Width}" Height="{Binding Height}"
StrokeThickness="0.75" Stroke="{Binding Brush}" x:Name="Ellipse" ToolTip="{Binding Text}">
</Ellipse>
</DataTemplate>
The Line will not need it. The code for this attached property might look like this:
public class CanvasPointProxyBehavior
{
public static readonly DependencyProperty PointProperty = DependencyProperty.RegisterAttached("Point", typeof(Point), typeof(CanvasPointProxyBehavior), new UIPropertyMetadata(null, PointChangedCallback));
public static void SetPoint(DependencyObject depObj, Point point)
{
depObj.SetValue(PointProperty, point);
}
public static Point GetPoint(DependencyObject depObj)
{
return depObj.GetValue(PointProperty) as Point;
}
private static void PointChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
UIElement uiElement = (dependencyObject as UIElement);
if (uiElement == null) return;
UIElement elementToBePositioned = uiElement;
var visualParent = VisualTreeHelper.GetParent(uiElement);
if (visualParent is ContentPresenter)
{
elementToBePositioned = visualParent as ContentPresenter;
}
var point = e.NewValue as Point;
if (point != null)
{
Canvas.SetLeft(elementToBePositioned, point.X);
Canvas.SetTop(elementToBePositioned, point.Y);
}
}
}
Hoping someone will find one or both solution useful.

Please note that I encountered the same ReSharper warning message as #ZoolWay, but in my case it was within a data grid, where I wanted the button on the right to be right-aligned instead of left-aligned:
Attached property setting "Grid.Column" has no effect in current
context and can be removed.
Here is the code where I had the warning, on the Button Grid.Column="2":
<Border VerticalAlignment="Center" Style="{DynamicResource InspectionsCustomDataGridHeader}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="10*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Label Name="InspectionsDataGridTitle" Background="White" Content="{Binding InspectionsCollectionView.View.Count, ConverterParameter={x:Static resx:Resources.InspectionsDataGridTitle}, Converter={StaticResource DataGridHeaderCountConverter}}" Style="{DynamicResource InspectionsCustomDataGridHeaderLabel}" MouseDown="InspectionsDataGridTitle_MouseDown" />
<!-- more labels, etc... -->
<Button Grid.Column="2" Width="30" Height="30" Margin="0,0,10,0" Background="Transparent" BorderThickness="0" HorizontalAlignment="Right" Command="{Binding CreatePDFCommand,Mode=OneWay}" >
<StackPanel Orientation="Horizontal" Background="Transparent">
<Image Width="30" Height="30" Source="/Inspections;component/Icons/pdf_btn.png" />
</StackPanel>
</Button>
</StackPanel>
</Grid>
</Border>
This is how I fixed the warning, where I moved the button below the first StackPanel:
<Border VerticalAlignment="Center" Style="{DynamicResource InspectionsCustomDataGridHeader}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Label Name="InspectionsDataGridTitle" Background="White" Content="{Binding InspectionsCollectionView.View.Count, ConverterParameter={x:Static resx:Resources.InspectionsDataGridTitle}, Converter={StaticResource DataGridHeaderCountConverter}}" Style="{DynamicResource InspectionsCustomDataGridHeaderLabel}" MouseDown="InspectionsDataGridTitle_MouseDown" />
<!-- more labels, etc... -->
</StackPanel>
<Button Grid.Column="2" Width="30" Height="30" Margin="0,0,10,0" Background="Transparent" BorderThickness="0" HorizontalAlignment="Right" Command="{Binding CreatePDFCommand,Mode=OneWay}" >
<StackPanel Orientation="Horizontal" Background="Transparent">
<Image Width="30" Height="30" Source="/Inspections;component/Icons/pdf_btn.png" />
</StackPanel>
</Button>
</Grid>
</Border>
HTH

Related

How to use Binding in trigger, to change another control in WPF?

I have this DataTemplate in style. I have two properties for Image's Source (Image and ImageHover). Now its Image. So I want to change it to ImageHover when the List item from the List Box is hovered by the mouse. How can I make this kind of Binding - Binding in trigger, to change another control's property ?
<DataTemplate x:Key="MetroListBoxItemTemplate">
<Grid Name="grid_menu">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="110"/>
</Grid.ColumnDefinitions>
<Image Name="menu_image" Source="{Binding Image}" Stretch="Uniform" Width="40" Height="40" >
</Image>
<StackPanel Grid.Column="1" Margin="5">
<TextBlock Text="{Binding Path=Title, FallbackValue=Title}" FontFamily="Segoe UI Light" FontSize="20"
Foreground="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}, Path=Foreground}" />
</StackPanel>
</Grid>
</DataTemplate>
I use the template here
<ListBox
x:Name="main_menu" HorizontalAlignment="Left" Margin="8,8,0,8" Width="160"
ItemTemplate="{DynamicResource MetroListBoxItemTemplate}"
ItemContainerStyle="{DynamicResource MetroListBoxItemStyle}"
ItemsSource="{Binding Menu}">
</ListBox>

WPF - ControlTemplate as Thumb control Template data binding

I have a custom MyThumb user control which implements Thumb class. This user control is templated using ControlTemplate which ItemsSource is bound to a ObservableCollection of my custom class with three properties. ObservableCollection is in public property in code behind named ImportFileColumns and it's populated before InitializeComponent(). Problem is, that ObservableCollection is populated with correct data, but ControlTemplate never gets data bound (TextBlock control should be populated using values from ColumnName property), so my assumption is that I'm doing something wrong in the XAML.
Here's XAML:
<ControlTemplate x:Key="columnsThumbTemplate">
<Grid Width="175" >
<ItemsControl Name="isTest" ItemsSource="{Binding ImportFileColumns}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="175">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="25" />
</Grid.ColumnDefinitions>
<Border BorderBrush="LightGray" BorderThickness="1,0,1,1" Background="White" Padding="10,5" Grid.Column="0">
<StackPanel Orientation="Horizontal" >
<TextBlock Text="{Binding Path=ColumnName}" Height="13" />
</StackPanel>
</Border>
<my:MyConnector x:Name="myRightConnector"
Template="{StaticResource tplRightConnector}"
Grid.Column="1"
IsConnectorClicked="False"
PreviewMouseLeftButtonDown="myConnector_PreviewMouseLeftButtonDown"
Canvas.Left="Auto"
Canvas.Right="Auto"
DragDelta="onDragDelta"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</ControlTemplate>
<my:MyThumb x:Name="myCustomColumnsThumb"
Canvas.Left="272"
Canvas.Top="400"
Template="{StaticResource columnsThumbTemplate}"
DragDelta="onDragDelta"
PreviewMouseLeftButtonDown="connector_PreviewMouseLeftButtonDown" />

Sharing control instance within a view in WPF

I'm having some issues with the wpf tab control. Not sure the title of the question is right I will refine it accoring to answers.
I want to create a simple panel system. I want to inject ot my "panel viewModel" 2 view model
MainViewModel will be display as the main area
PanelViewModel will be display as a panel on the right hand side of the view
the panelViewModel will be hidden by default and a button will display it on top of the main view model when needed
The view look like this:
<UserControl.Resources>
<DataTemplate x:Key="MainWindowTemplate" DataType="{x:Type UserControl}">
<ContentPresenter Content="{Binding DataContext.MainViewModel, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid Visibility="{Binding IsPanelHidden, Converter={StaticResource bool2VisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentControl Grid.Column="0" ContentTemplate="{StaticResource MainWindowTemplate}" />
<Button Grid.Column="1" Content="{Binding PanelTitle}" Command="{Binding Path=ShowPanelCommand}">
<Button.LayoutTransform>
<RotateTransform Angle="90"/>
</Button.LayoutTransform>
</Button>
</Grid>
<Grid Visibility="{Binding IsPanelHidden, Converter={StaticResource revertBool2VisibilityConverter}}">
<ContentControl ContentTemplate="{StaticResource MainWindowTemplate}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.5*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="2" VerticalAlignment="Stretch" Background="Red" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border HorizontalAlignment="Stretch">
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding PanelTitle}" Margin="5,5,0,2" HorizontalAlignment="Left"></TextBlock>
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="0,0,5,0" HorizontalAlignment="Right" >
<Button Content="Minimze" Command="{Binding HidePanelCommand}"/>
</StackPanel>
</Grid>
</Border>
<ContentPresenter Grid.Row="1" Margin="2" Content="{Binding PanelViewModel}" VerticalAlignment="Top"/>
</Grid>
</Border>
</Grid>
</Grid>
The view model look like that:
public class TestTabViewModel : ObservableObject
{
#region private attributes
#endregion
public TestTabViewModel(string panelName, object panelViewModel, object mainViewModel)
{
IsPanelHidden = true;
PanelTitle = panelName;
PanelViewModel = panelViewModel;
MainViewModel = mainViewModel;
ShowPanelCommand = new DelegateCommand(() =>ManagePanelVisibility(true));
HidePanelCommand = new DelegateCommand(() => ManagePanelVisibility(false));
}
#region properties
public string PanelTitle { get; private set; }
public bool IsPanelHidden { get; private set; }
public object PanelViewModel { get; private set; }
public object MainViewModel { get; private set; }
public DelegateCommand ShowPanelCommand { get; private set; }
public DelegateCommand HidePanelCommand { get; private set; }
#endregion
#region private methods
private void ManagePanelVisibility(bool visible)
{
IsPanelHidden = !visible;
RaisePropertyChanged(() => IsPanelHidden);
}
#endregion
}
So for so good, this system work fine I aslo added some pin command but I remove them from here to make it "simple".
My problem come when the main view model hold a tab control. In this, case if I select a tab and "open" the panel, the tab selected is "changed". In fact it's not changed it's just that I display another contentControl which is not synchronize with the previouse one. I guess that the view instance is not the same even if the viewmodel behind is.
So how do I share a view instance within a view (or have the selection process synchornized)? My first guest was to use the datatemplate (as show in the example) but it did not solve my problem.
By the way, I know some third-party handling panel docking pin ... (eg avalon) but all the one I found are really too much for my simple need.
Thanks for the help
Your best bet would probably be to replace your two Grids with a single ContentControl, and switch the Template on button click or in a Trigger. This way your actual Content (the TabControl) will be the same, but the template used to display the Content will change
Here's a quick example:
<Window.Resources>
<ControlTemplate x:Key="Grid1Template" TargetType="{x:Type ContentControl}">
<DockPanel>
<Grid Background="CornflowerBlue" Width="100" DockPanel.Dock="Left" />
<ContentPresenter Content="{TemplateBinding Content}" />
</DockPanel>
</ControlTemplate>
<ControlTemplate x:Key="Grid2Template" TargetType="{x:Type ContentControl}">
<DockPanel>
<Grid Background="CornflowerBlue" Width="100" DockPanel.Dock="Right" />
<ContentPresenter Content="{TemplateBinding Content}" />
</DockPanel>
</ControlTemplate>
</Window.Resources>
<DockPanel>
<ToggleButton x:Name="btnToggle" Content="Toggle View" DockPanel.Dock="Top" />
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Template" Value="{StaticResource Grid1Template}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=btnToggle, Path=IsChecked}" Value="True">
<Setter Property="Template" Value="{StaticResource Grid2Template}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
<TabControl>
<TabItem Header="Tab1" />
<TabItem Header="Tab2" />
<TabItem Header="Tab3" />
</TabControl>
</ContentControl>
</DockPanel>
i dont know if i get what you want but i think that you could do the following.
i assume that you want some "MainViewmodeldata" be presented as your tabcontrol.
so i woulf first create a datatemplate for this.
<UserControl.Resources>
<DataTemplate DataType="{x:Type MainViewmodeldata}">
<TabControl>
<TabItem Header="Tab1">
<TextBlock Grid.Column="1" Text="Tab1Content"/>
</TabItem>
<TabItem Header="Tab2">
<TextBlock Grid.Column="1" Text="Tab2Content"/>
</TabItem>
</TabControl>
</DataTemplate>
</UserControl.Resources>
now i would just bind my this mainviewmodeldata to the contentcontrol and let wpf render it for you. i really dont know if you still need these two grids, cause i dont know what you wanna achieve.
<Grid x:Name="Grid1" Visibility="{Binding IsPanelHidden, Converter={StaticResource bool2VisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentControl Content="{Binding MainViewmodelData}" />
<StackPanel Grid.Column="1">
<Button x:Name="Button1" Content="Switch look" Command="{Binding ShowPanelCommand}"/>
<TextBlock Text="Look1"/>
</StackPanel>
</Grid>
<Grid x:Name="Grid2" Visibility="{Binding IsPanelHidden, Converter={StaticResource revertBool2VisibilityConverter}}">
<ContentControl Content="{Binding MainViewmodelData}" />
<StackPanel HorizontalAlignment="Right">
<Button x:Name="Button2" Content="Switch look" Command="{Binding HidePanelCommand}"/>
<TextBlock Text="Look2"/>
</StackPanel>
</Grid>
</Grid>

Using HierarcicalDataTemplates in conjunction with TreeViewItem control templates

I am having some difficulty figuring out how to template the following TreeView item layout:
I have several items, SearchList, which contains a collection of Search, which contains a collection of DataSet (sort of, but that is beside the point). What I am having difficulty with is styling each node level the way I want. I am using MVVM, and the TreeViews ItemsSource property is set to an ObservableCollection of SearchListViewModels which in turn contain my objects all the way down the object tree.
I can successfully style the SearchList HierarchicalDataTemplate to display them correctly. Where I get hung up is on SearchTerm nodes styling. I want the DataSets to be represented in a wrap panel or uniform grid (I haven't decided yet) to the right of the SearchTerm content area. I have modified a TreeViewItem control template to behave this way I think), however if I set it in the ItemContainerStyle property of the Search HierarchicalDataTemplate, it does nothing. All that gets displayed is the content for the Search.
My Altered TreeViewItem Template
<Style TargetType="{x:Type TreeViewItem}" x:Key="AlteredTreeViewItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
MinWidth="19" />
<ColumnDefinition Width="0.414*" />
<ColumnDefinition Width="0.586*"/>
</Grid.ColumnDefinitions>
<Border x:Name="Bd" HorizontalAlignment="Stretch"
Grid.Column="1" Grid.ColumnSpan="1" Background="#7F058956">
<ContentPresenter x:Name="PART_Header" Margin="10,0" />
</Border>
<WrapPanel x:Name="ItemsHost"
Grid.Column="2" IsItemsHost="True"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My Search Hierarchical Data Template
<HierarchicalDataTemplate DataType="{x:Type local:SearchViewModel}" ItemsSource="{Binding MySearch.Custodians}" ItemContainerStyle="{StaticResource AlteredTreeViewItem}">
<TextBlock Text="{Binding MySearch.SearchName}" Foreground="Black" FontFamily="Arial" FontSize="16"/>
</HierarchicalDataTemplate>
Surely it is possible to both style differently and have child items laid out differently? How can this be achieved?
It seems that you are pretty close to what you're after. I tried to recreate your scenario based on the code you posted and I noted some problems with it (which of course are based on my interpretation of the code you posted)
You are missing the ContentSource="Header" part of the ContentPresenter
I think you are applying the ItemContainerStyle at the wrong HierarchicalDataTemplate level. It should be specified on the parent in order to affect the children (in your case SearchListViewModel).
The default Template for TreeViewItem lays out the ContentPresenter in an Auto sized ColumnDefinition so the WrapPanel won't succesfully wrap unless you modify the ItemContainerStyle for the parent as well. I changed it to a UniformGrid in my sample below
With the changes from above and a few other things I got a result that looks like this which hopefully is pretty close to what you're after
I uploaded the sample solution here: https://www.dropbox.com/s/4v2t8imikkagueb/TreeViewAltered.zip?dl=0
And here is the Xaml code for it (too much code to post it all..)
<Window.Resources>
<!-- DataSet-->
<HierarchicalDataTemplate DataType="{x:Type data:DataSet}">
<Border BorderThickness="3"
BorderBrush="Gray"
Background="Green">
<TextBlock Text="{Binding Path=Tables[0].TableName}"
Margin="5"/>
</Border>
</HierarchicalDataTemplate>
<!-- SearchViewModel -->
<HierarchicalDataTemplate DataType="{x:Type viewModel:SearchViewModel}"
ItemsSource="{Binding DataSets}">
<TextBlock Text="{Binding DisplayName}"
Foreground="Black"
FontFamily="Arial"
FontSize="16"/>
</HierarchicalDataTemplate>
<!-- SearchListViewModel -->
<HierarchicalDataTemplate DataType="{x:Type viewModel:SearchListViewModel}"
ItemsSource="{Binding SearchList}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="19" />
<ColumnDefinition Width="0.414*" />
<ColumnDefinition Width="0.586*"/>
</Grid.ColumnDefinitions>
<Border x:Name="Bd"
HorizontalAlignment="Stretch"
Grid.Column="1"
Grid.ColumnSpan="1"
Background="#7F058956">
<ContentPresenter x:Name="PART_Header"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</Border>
<UniformGrid x:Name="ItemsHost"
Grid.Column="2"
Columns="3"
IsItemsHost="True"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<TextBlock Text="{Binding DisplayName}"
FontSize="20"/>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding SearchListViewModels}" />
</Grid>
Something I learnt a long time ago when trying to create a similar interface was that you are better using a ListBox than a TreeView.
Why?
If you only have one level of expansion (as it appears from your sample) you will a lot more control of the layout as you have a single DataTemplate to style.
It is lot easier to customize a ListBox than a TreeView as you do not have be concerned with the GridViewColumnHeader and GridViewColumnPresenters etc.
To get the expansion part (which is why you initially selected a TreeView), simply use a Grid with two rows defined and an Expander in the second row bound to the IsChecked property of a ToggleButton. See the example that I pulled from my Log Viewer.
<DataTemplate>
<Grid Margin="0,0,0,3" Grid.IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" SharedSizeGroup="SSG_TimeIcon"/>
<ColumnDefinition Width="120" SharedSizeGroup="SSG_Time"/>
<ColumnDefinition Width="30" SharedSizeGroup="SSG_LevelIcon"/>
<ColumnDefinition Width="70" SharedSizeGroup="SSG_Level"/>
<ColumnDefinition Width="*" SharedSizeGroup="SSG_Message"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- ProgramTime -->
<Rectangle Grid.Column="0" Grid.Row="0" Margin="0,0,0,0" Width="16" Height="16" VerticalAlignme="Top" HorizoalAlignme="Stretch" Fill="{StaticResource Icon_Timer}"/>
<TextBlock Grid.Column="1" Grid.Row="0" Margin="5,0,0,0" VerticalAlignme="Top" HorizoalAlignme="Stretch" Text="{Binding Path=TimeStamp, Converter={StaticResource ObjectToStringConverter}}" ToolTip="{Binding Path=ProgramTime}"/>
<!-- Level -->
<Rectangle Grid.Column="2" Grid.Row="0" Margin="10,0,0,0" Width="16" Height="16" VerticalAlignme="Top" HorizoalAlignme="Stretch" Fill="{Binding Path=Level, Converter={StaticResource MappingConverterNinjaLogLevelEnumToBrushResource}}"/>
<TextBlock Grid.Column="3" Grid.Row="0" Margin="5,0,0,0" Text="{Binding Path=LevelFriendlyName}" VerticalAlignme="Top" HorizoalAlignme="Stretch"/>
<!-- Message -->
<StackPanel Grid.Column="4" Grid.Row="0" Margin="10,0,0,0" Orieation="Horizoal" >
<TextBlock Margin="0,0,0,0" Text="{Binding Path=LogMessage}" TextWrapping="Wrap" VerticalAlignme="Top" HorizoalAlignme="Stretch"/>
<ToggleButton x:Name="ExpandExceptiooggleButton" VerticalAlignme="Top" Margin="5,0,0,0" IsChecked="False"
Coe="Show Details" Tag="Hide Details" Style="{StaticResource TextButtonStyle}"
Foreground="{StaticResource BlueBrush}" Background="{StaticResource RedBrush}"
Visibility="{Binding Path=HasException, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
<Expander IsExpanded="{Binding Path=IsChecked, ElemeName=ExpandExceptiooggleButton}" Style="{StaticResource CoeExpanderStyle}"
Margin="10,0,0,0" Grid.Column="4" Grid.Row="1">
<Border BorderBrush="{StaticResource DarkGreyBrush}" BorderThickness="1,0,0,0">
<TextBlock Text="{Binding Path=Exception}" Margin="5,0,0,0"/>
</Border>
</Expander>
</Grid>
</DataTemplate>
Can you see how much easier it is to define a header and expandable body. If you do have a need for nested data, add a Level property your view model (you are using MVVM aren't you?!) and then create a IValueConverter that returns a Margin (i.e. Thickness) to fake the indent.

Windows Phone 7 Silverlight DataTemplate HyperlinkButton content

I'm trying to make a list that is populated by external data and I have a datatemplate for it.
Each element of the list is composed of an image and two textblocks, and is linked to a specific page.
I'm trying to do this but when I wrap the structure with an HyperlinkButton, I just get a blank page.
I don't know if I'm doing something stupid wrong or it's not possible to have so many items in an HyperlinkButton. If it's not possible to do it this way, can someone guide me to the best solution to do this? Here's the code:
<Grid x:Name="ContentPanel" Margin="0,140,0,0" Background="White">
<ListBox HorizontalAlignment="Stretch" Name="itemList" VerticalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<HyperlinkButton>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="480">
<Grid.Background>
<ImageBrush ImageSource="/app;component/Images/degradat_cela.png" Stretch="UniformToFill" AlignmentY="Top" AlignmentX="Left" />
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="35" />
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="430*" />
</Grid.ColumnDefinitions>
<Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="38" Width="38" Grid.RowSpan="2" Grid.Column="0" Grid.Row="0" BorderThickness="1" BorderBrush="#FFFF003F" Padding="1">
<Image HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Name="listImage" Width="36" Height="36" Source="{Binding image}" />
</Border>
<TextBlock Margin="5 12 0 0" Grid.Column="1" Grid.Row="0" Name="title" Foreground="Black" Text="{Binding title}" FontWeight="Bold" FontSize="18" />
<TextBlock Margin="5 0 0 8" Grid.Column="1" Grid.Row="1" Name="description" Foreground="Black" Text="{Binding subtitle}" FontSize="14" />
</Grid>
</HyperlinkButton>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I will also accept any suggestions to make my code better, as I'm new to .NET and I probably don't do things the best way!
Remove the HyperlinkButton and instead use the SelectionChanged event of the ListBox. So add this property to your ListBox:
SelectionChanged="myListBox_SelectionChanged"
Then in your code behind do this:
private void myListBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if ((sender as ListBox).SelectedIndex == -1)
return;
NavigationService.Navigate(new System.Uri(string.Format("/Drilldown.xaml?Index={0}",(sender as ListBox).SelectedIndex),System.UriKind.Relative));
}
This code assumes a drilldown page that uses a query string to change it's layout. This is just for example. If instead of index you wanted to reference some property of the bound item you could instead do something like this (assuming an ID property):
int itemID = ((sender as ListBox).SelectedItem as MyApp.Model.myItem).ID;
NavigationService.Navigate(new System.Uri(string.Format("/Drilldown.xaml?ID={0}",itemID),System.UriKind.Relative));

Resources