Passing Non-Item Values to Properties in ItemTemplate - c#

Today I'm having trouble passing values from a parent control down to the properties of a child control in a list.
I have a custom control which I've made which functions as a Thumbnail Check Box. Essentially it's just a checkbox wrapped around an image with some nice borders. It's all wrapped up into a DLL and deployed as a custom control
If I want to use a single instance of the control, I can do so like this...
<tcb:ThumbnailCheckBox IsChecked="True"
ImagePath="D:\Pictures\123.jpg"
CornerRadius="10"
Height="{Binding ThumbnailSize}"
Margin="10" />
Code Listing 1 - Single Use
This works great, and easily binds to ThumbnailSize on my ViewModel so I can change the size of the image in the control however I want.
The problem is when I want to expand the use of this control into a list, I'm running into a few problems.
To begin, I've styled the ListBox control to meet my needs like so...
<Style TargetType="{x:Type ListBox}"
x:Key="WrappingImageListBox">
<!-- Set the ItemTemplate of the ListBox to a DataTemplate
which explains how to display an object of type BitmapImage. -->
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<tcb:ThumbnailCheckBox ImagePath="{Binding ImagePath}"
IsChecked="{Binding Selected}"
Height="{TemplateBinding utilities:MyAttachedProperties.ImageSize}"
CornerRadius="8"
Margin="10">
</tcb:ThumbnailCheckBox>
</DataTemplate>
</Setter.Value>
</Setter>
<!-- Swap out the default items panel with a WrapPanel so that
the images will be arranged with a different layout. -->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<!-- Set this attached property to 'Disabled' so that the
ScrollViewer in the ListBox will never show a horizontal
scrollbar, and the WrapPanel it contains will be constrained
to the width of the ScrollViewer's viewable surface. -->
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Disabled" />
</Style>
Code Listing 2 - ListBox Style
And I call it like this from my main view...
<ListBox ItemsSource="{Binding DirectoryPictures}"
Grid.Row="1"
Style="{DynamicResource WrappingImageListBox}"
Background="Transparent"
util:MyAttachedProperties.ImageSize="500"/>
Code Listing 3 - Main Call
This works exactly as I'd like, except for the ImageSize property. Both ImagePath and Selected are properties of the individual list items being bound to the ListBox.
As you can see, I created an attached property to try to pass the value (500), but it doesn't seem to be working. I should note that I think the style I've created is correct because the elements use the default value.
public static class MyAttachedProperties
{
public static double GetImageSize(DependencyObject obj)
{
return (double)obj.GetValue(ImageSizeProperty);
}
public static void SetImageSize(DependencyObject obj, double value)
{
obj.SetValue(ImageSizeProperty, value);
}
public static readonly DependencyProperty ImageSizeProperty =
DependencyProperty.RegisterAttached(
"ImageSize",
typeof(double),
typeof(MyAttachedProperties),
new FrameworkPropertyMetadata(50D));
}
Code Listing 4 - Attached Property
The 50D specified on the last line is applying to the listed control. If I change it, and recompile, the end result changes. But the sent value of 500 I specified in my ListBox Main call (listing 3) is not ever sent. Of course, I would eventually like to change the 500 into a bound property on my view model, but I won't do that until I get it working with an explicit value.
Can someone help me figure out how to send a value from my main ListBox call (listing 3) and apply it to the individual items that are populated by the template? The other properties I have work, but they are a properties of each item in the List I'm binding to the ListBox, whereas ImageSize is not.
EDIT To address First Response
This seems to be working, but it's kind of peculiar. My listbox is now being called like so...
<ListBox ItemsSource="{Binding DirectoryPictures}"
Grid.Row="1"
Style="{DynamicResource WrappingImageListBox}"
Background="Transparent" />
And I've changed my style to the code you suggested...
<tcb:ThumbnailCheckBox ImagePath="{Binding ImagePath}"
IsChecked="{Binding Selected}"
Height="{Binding Path=DataContext.ThumbnailSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}}"
CornerRadius="8"
Margin="10">
My only concern is, now the style is accessing the ViewModel for that control directly rather than receiving a bound value.
Suppose I wanted to use the ListBox again, but on another UserControl whose ViewModel didn't have ThumbnailSize property, but used one by another name?
You see where I'm going with this... the current solution is not very extensible and is limited to the current classes as they are named exactly.
In fact, in a perfect world, I'd like to have variable names for the ImagePath and Selected properties, but that's a different discussion.

It's possible to use FindAncestor. The idea of that is, child traverses through logical tree, and tries to find parent with concrete type (in this case, ListBox), and then accesses attached property. See http://wpftutorial.net/BindingExpressions.html for more binding expressions.
In your ItemTemplate, this is how you could access ThumbnailSize property:
{Binding Path=(util:MyAttachedProperties.ImageSize),
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBox}}}
Essentially, the question asked here was a little bit opposite, but results are same. "How could items in ListBox access ListBox (attached) properties.

Related

WPF ContentPresenter Content null when Visibility=Collapsed

I'm making custom control with edit/view state.
I've made 2 dependencyProperties with default styles:
<Setter Property="EditContent">
<Setter.Value>
<TextBox Text="{Binding ElementName=parent, Path=LocalValue}" />
</Setter.Value>
</Setter>
<Setter Property="ViewContent">
<Setter.Value>
<TextBox IsEnabled="False" Text="{Binding ElementName=parent, Path=LocalValue}" />
</Setter.Value>
</Setter>
and then, displaying these Contents depending on IsReadOnly value like this:
<Border Background="Transparent"
MouseLeftButtonDown="UIElement_OnMouseLeftButtonDown"
Visibility="{Binding ElementName=parent,
Path=IsReadOnly,
Converter={StaticResource BooleanToCollapsingVisibilityConverter},
ConverterParameter=true}">
<ContentPresenter Content="{Binding ElementName=parent, Path=ViewContent}" />
</Border>
Problem is, that when my control loads with IsReadOnly = true, Content Property of my ContentPresenter for EditContent is null.
When I'm changing IsReadOnly to false Content of EditContent loads, but my binding does not work (like it's not evaluated).
How to re-evaluate bindings in WPF, or force ContentPresenter to load it's content on created (even if it's invisible)?
P.S. If I navigate to this ContentPresenter in Snoop (or WPF Inspector) when It's invisible - it's empty. When I navigate to it when it's visible - bindings starting to work
Please, have a look at output windows while debugging. you will see errormessage describing the binding problem. wpf rule nr.1: always check output window.
The reason is that your edit / view content has different NameScope, therefore ElementName does not work. However, in your Control you can set NameScope manually, by using something like:
var currentScope = NameScope.GetNameScope(this);
NameScope.SetNameScope((UIElement)this.EditContent, currentScope)
in your case you are using styles and styles has its own namescope, so it won't work. Imagine, that you used the style on multiple pages. What element should be used?
Sometimes you can use Source={x:Reference elementName}, but you cannot use it in direct children of the source the element, because the element does not exist yet, when the {x:Reference } is being resolved
never set content-like properties inside styles. if you applied your style to more than one element, that the same TextBox from ViewContent would be added to visual tree multiple times and that throws an exception. You should use DataTemplates instead of direct content in styles

Caliburn.Micro: Grid not getting binded/linked to x:Name

So I'm developing a Windows Phone 8 app with the Caliburn.Micro framework. I'm trying to create a grid where I, at runtime add/remove elements such as TextBlock's at runtime. I've tried a few things to bind my code to the x:Name but nothing has worked so far.
So one of the things i tried was having a placeholder grid in my xaml aka View:
<Grid x:Name="ContentPanel" Margin="0,97,0,0" Grid.RowSpan="2">
</Grid>
And then i my ViewModel i use the following to bind my ContentPanel Grid:
private Grid contentPanel;
public Grid ContentPanel
{
get
{
return contentPanel;
}
set
{
contentPanel = value;
NotifyOfPropertyChange(() => ContentPanel);
}
}
I then created a TextBlock to add to the grid:
TextBlock txt1 = new TextBlock();
txt1.Text = "2005 Products Shipped";
txt1.FontSize = 20;
txt1.FontWeight = FontWeights.Bold;
Grid.SetRow(txt1, 1);
And finally i added the TextBlock to my Grid:
ContentPanel.Children.Add(txt1);
When i run this code ContentPanel turn out to be equals null, why is that? Shouldn't Caliburn auto bind ContentPanel x:Name="ContentPanel" with the property ContentPanel?
I would appreciate your help in this matter.
My core problem, that i need solved is this:
I got a login page in my app where i show some pictures and text loaded from a server. As you can see below this is done with Image and a TextBlock When that server is offline or the wi-fi simply aren't enabled i want to replace this picture+text with a static image. Aka i want to remove the TextBlock from the StackPanel.
The part where i load and show the stuff form my server works great and looks like this in my xaml:
<StackPanel Orientation="Horizontal" Background="White" DataContext="{Binding FeedItemsAnnounce,Mode=TwoWay}" >
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" Margin="5" Width="170" Height="138">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
So when the server is offline/wifi disabled i want to replace that with. so that the TextBlock is no longer there:
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAdvertisement" >
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
Is this even possible? If not what would the best semi-solution be?
EDIT 1: I've managed to setup the flow following the instructions from the accepted answer. But my BooleanToVisibilityConverter is not called, though my NotifyOfPropertyChange(() => IsConnectionAvailable); is getting called.
My Property:
private bool _isConnectionAvailable;
public bool IsConnectionAvailable
{
get { return _isConnectionAvailable; }
set
{
if (_isConnectionAvailable != value)
{
_isConnectionAvailable = value;
NotifyOfPropertyChange(() => IsConnectionAvailable);
}
}
}
How i change the bool: This code is called in my constructor for my ViewModel(just as a test to see if it was working):
IsConnectionAvailable = false;
TextBlock (without trigger code cause its the same as previous):
<TextBlock Text="{Binding Title}" Visibility="{Binding IsConnectionAvailable, Converter={StaticResource BoolToVisibility}}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
It's like the Binding IsConnectionAvailable isn't working because i can change the name IsConnectionAvailable in my Xaml to anything and my NotifyOfPropertyChange(() => IsConnectionAvailable); will still be called.
Any ideas?
I can't even do a normal bind Visibility="{Binding Path=IsVisibil,Mode=TwoWay} to a public Visibility IsVisibil property. I've done this in other classes, but even this won't work??
EDIT 2: The problem that course the binding not to work, seems to lie somewhere in this code:
<StackPanel Orientation="Horizontal" Background="White" DataContext="{Binding FeedItemsAnnounce,Mode=TwoWay}" >
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" Margin="5" Width="170" Height="138">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
<TextBlock Text="{Binding Title}" Visibility="{Binding Path=IsVisibil,Mode=TwoWay}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</StackPanel>
Solution to EDIT 1 and 2: I created an x:Name"Root" at the top of my xaml structure. Then changed the binding to:
ElementName=Root, Path=DataContext.IsVisibil
This is needed because the binding to visibility that I'm trying to set is inside another DataContxt.
This isn't the correct way to use CM, there are a number of areas where you are confusing the model and viewmodel and the binding functionality in CM.
What you are doing currently
You are attempting to have the CM framework look for a property called ContentPanel on your ViewModel and automatically figure out what properties on Grid to bind it to...
This won't work because of a few reasons:
I don't think there is a convention for Grid in CM - it's not really bindable in an obvious way (it's a layout container)
Grid is not a data enabled control - it doesn't know how to consume a collection and display dynamic rows out the box (it's a layout container)
What you are doing doesn't really make any sense (you have an instance of a grid in your UserControl and you have also instantiated a grid in your ViewModel - these are two separate instances of a control - you can't 'bind' them together - that's not how it all works)
CM and Bindings
When you using element name bindings e.g. x:Name with CM, it attempts to find a property on the ViewModel which matches the element name. At this point, depending on the conventions setup for the source control in question, CM will attempt to automagically wire up all the bits and pieces.
There are default conventions contained in ConventionManager which determine which properties to bind when you use element name bindings - e.g. for TextBlock, the Text property on the TextBlock is bound to the target property on the ViewModel.
http://caliburnmicro.codeplex.com/SourceControl/latest#src/Caliburn.Micro.Platform/ConventionManager.cs - look at the class constructor on ConventionManager to see the out of the box conventions - there isn't one for Grid
Once a target property is found, CM will bind it up.
(As an aside: it's worth noting that if the control type is a ContentControl CM will do some composition magic so you can have viewmodels that contain other viewmodels and have a composition all bound up at runtime - great for screens which have multiple sub-windows etc)
The problem you have is that there is no convention setup for Grid out of the box - this is most likely because a Grid in SL/WPF is primarily used for layout, and is not really a 'data container' or data aware in any way (apart from the few dependency properties you can bind to) - i.e. I don't think it's possible to bind to a grid and get a dynamic number of columns/rows without some customisation to the control, hence the omission of any conventions
(think about it - if you are binding a grid to a collection, what should the grid do... add rows or columns? It can't really be supported in a sensible way)
Now bringing it back to SL/WPF for a sec:
Usually if you want a variable list of items you will need to bind to the ItemsSource property of a control which inherits from ItemsControl (or ItemsControl itself).
Many controls do this: if they need to display a dynamic number of items they will usually inherit from ItemsControl.
How does this tie in with CM?
Caliburn Micro knows how to bind up ItemsControl out of the box. This means you can have a property on your ViewModel containing a collection of items and after binding you get a dynamic view of these at runtime
For example - a CM bound ItemsControl might look like this:
<ItemsControl x:Name="TextItems">
<!-- host the items generated by this ItemsControl in a grid -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- render each bound item using a TextBlock-->
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SomeTextualProperty}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now you just need a collection of objects to bind this to - each item in the collection becomes a new item in the control with its DataContext pointing to the bound item. I've made the assumption that you would want each item to be a ViewModel which contained the property SomeTextualProperty - I've defined that here...
// Provides a viewmodel for a textual item
public class TextItemViewModel
{
public string SomeTextualProperty { get; set;}
}
The VM that should contain the list of items would need to have a collection to bind against.
(Note: Since you are adding items to it at runtime you need to tell the UI when the collection changes - ObservableCollection gives you this for free as it implements collection changed notification events)
// This is the viewmodel that contains the list of text items
public class ScreenViewModel
{
public ObservableCollection<TextItemViewModel> TextItems { get; set; }
}
What else I would consider the incorrect approach
Your ViewModels shouldn't know about your View implementation i.e. they shouldn't reference any type of controls unless absolutely necessary (I can't think of a time when I had to put a control in a VM). ViewModels should model the view - but they shouldn't really need to know any specifics about what that view contains - this way they are more easily testable and they are easily reused
If you follow the above approach, you can get away with providing an application which re-uses the set of viewmodels, but provides different views for each. You can try this by replacing ItemsControl with another type of control in the view (as long as it's data aware such as a datagrid) and the VM will still work - the VM is view agnostic.
Your use of Grid in your VM is not ideal because Grid is a visual control, it is not data. Remember that the visuals are your View and the ViewModel should just contain data and events which notify the view of things happening
If I was doing this - the code would look more like the code I posted above.
To sum up
Model the information you wanted to show in a ViewModel (TextItemViewModel)
Add a collection of these objects to the main ViewModel (ScreenViewModel) using a change aware collection such as ObservableCollection
Add/remove items from the collection using the standard add/remove
Bind the ItemsControl in the view using x:Name bindings to the collection on your ScreenViewModel
Adding/removing items in the VM will fire property changed notifications. ItemsControl will watch for these events and update itself accordingly
Addendum
You could get away with just using an ObservableCollection<string> instead of a TextBlockViewModel but it's not clear if you want to add more properties to the items you are binding to the grid (such as IsHeading property for headings which you could then make bold/italic in the view)
If you want to just use strings just modify the DataTemplate to bind directly to the DataContext rather than a property on the DataContext
<ItemsControl x:Name="TextItems">
<!-- host the items generated by this ItemsControl in a grid -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- render each bound item using a TextBlock-->
<ItemsControl.ItemTemplate>
<DataTemplate>
**<TextBlock Text="{Binding}"/> <!-- Bind direct -->**
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit:
Ok in your case it's quite simple - your ViewModel should simply model the state of the server:
public class LoginPageViewModel
{
public bool IsConnectionAvailable { get; set; } // or whatever your variable should be called
}
Then bind the visibility of the textblock to this using a converter:
<TextBlock Visibility="{Binding IsConnectionAvailable, Converter={StaticResource BooleanToVisibilityConverter}}">
You will need to declare the static resource for the converter somewhere (in the control itself or your main resources dictionary for example)
It looks like there is a converter already defined in System.Windows.Controls somewhere, but in case you can't find it the implementation is pretty simple (you could probably do this a bit better to guard against invalid input but for brevity I've kept it tiny):
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool) value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter,CultureInfo culture)
{
throw new NotImplementedException();
}
}
You may also want to change the state from available/unavailable during the views lifecycle, so in that case you probably want to use the property changed events built in to PropertyChangedBase (which Screen also inherits) to let the view know when the property changes
private bool _isConnectionAvailable;
public bool IsConnectionAvailable
{
get { return _isConnectionAvailable; }
set
{
if (_isConnectionAvailable != value)
{
_isConnectionAvailable = value;
NotifyOfPropertyChange(() => IsConnectionAvailable);
}
}
}
Addendum 2
I prefer the terse CM syntax instead of being explicit when binding action messages - so your XAML would change from:
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAdvertisement" >
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
To
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128" cal:Message.Attach="[Tap] = [LoadAdvertisement($dataContext.Link)]"></Image>
(actually that might not be right with the $dataContext.Link part ... but then again it might be... see here: http://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Actions&referringTitle=Documentation)

Why does this ComboBox ignore the DataTemplate when SelectedItem is a ContentControl?

In our application we have a screen design feature which is comprised of a custom ScreenDesignPanel and a Property Grid with a ComboBox at the top which points to the selected item on the ScreenDesignPanel. This allows the user to select the UIElement via the ComboBox or via the mouse to set its properties. We achieve this by binding the ItemsSource of the ComboBox to the ScreenDesignPanel's Children collection, then binding their SelectedItems together. This works great.
However, for whatever reason, if the SelectedItem is a ContentControl or a subclass like Button the ItemTemplate specified for the ComboBox is ignored for the 'selected item area' but it is applied when displaying the item in the dropdown list. If the SelectedItem is not a ContentControl, the template is used in both cases.
This also is seemingly specific to the ComboBox. If we use any other selector control: ListBox, ListView, ItemsControl... even third-party ComboBox controls... they all work as expected, properly applying the DataTemplate. ComboBox is doing something internally which no other control is doing.
Note: Below is an over-simplified example for illustrative purposes of the issue only. It is not how we're actually using it as described above.
Also of note: In the DataTemplate for the ComboBox.ItemTemplate, we are only using properties (i.e. Foreground in the example), and are not displaying the DataContext (i.e. the actual ContentControl) itself. This is important because again, the actual control already exists on the ScreenDesignPanel and therefore can't be used for display in the ComboBox's ItemTemplate as it would have two parents which isn't allowed. In other words, it is being used purely as data here.
One last thing... we have a working solution in our app, which was to wrap the Children before binding it to the ComboBox.ItemsSource. However, I'm still curious as to why the ComboBox behaves the way it does which is SPECIFICALLY what I'm asking. (In other words, I'm not looking for other solutions to this design. We already have a working one. I'm looking for clarity on the odd behavior of the ComboBox itself.)
On to the code!
In the first example below, note how the data template is applied to everything in the dropdown, but the selected item area only uses a template if the selected item is not a ContentControl.
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="I am the template" Foreground="{Binding Foreground}" />
</DataTemplate>
</ComboBox.ItemTemplate>
<!-- Four 'Data' items for example only -->
<TextBlock Text="I am a Red TextBox" Foreground="Red"/>
<ListBox Foreground="Purple">
<ListBoxItem>I am a Purple ListBox</ListBoxItem>
</ListBox>
<ContentControl Content="I am a Blue ContentControl" Foreground="Blue" />
<Button Content="I am a Button with Green text" Foreground="Green" />
</ComboBox>
This second example shows that it is completely acceptable and fully supported to use a UIElement as the content of a ContentPresenter and still use a DataTemplate (via ContentTemplate) so you can use it in a purely-data role, allowing the template itself to define the visual appearance without displaying the UIElement itself, which is used purely as data here.
<ContentPresenter>
<ContentPresenter.ContentTemplate>
<DataTemplate>
<TextBlock Text="I am the ContentTemplate" Foreground="{Binding Foreground}" />
</DataTemplate>
</ContentPresenter.ContentTemplate>
<ContentPresenter.Content>
<Button Content="I am the button" Foreground="Green" />
</ContentPresenter.Content>
</ContentPresenter>
Again, the issue is specific to a ComboBox. I want to find out why the data template isn't applied in that single case, and how to force it to be applied, if possible.
Of note, ComboBox does define SelectionBoxItemTemplate which is separate from the regular ItemTemplate but the rub is that is read-only so you can't set it. We really don't want to re-template the ComboBox as that can mess up proper theming.
Have you tried explicitly setting the DataTemplate to the ContentControl.ContentTemplate property?:
<UserControl.Resources>
<DataTemplate x:Key="DataTemplate">
<TextBlock Text="{Binding Content,
StringFormat='Displayed via template: {0}'}" />
</DataTemplate>
</UserControl.Resources>
...
<ContentControl Content="ContentControl"
ContentTemplate="{StaticResource DataTemplate}" />

Binding to Datagrid's ItemsSource collection properties instead of single items

I'm confused while trying to bind some properties housed inside a collection rather than the properties of the elements.
I'm not even sure how to phrase it right... code might explain better: here are the types (not actual code, I've shortened it to the basics):
public class myType
{
public int P {get;set;}
}
public class myTypeCollection : ObservableCollection<myType>
{
public int Ptotal {get { return this.Items.Select(i=>i.P).Aggregate((c,t)=>t = t + c); }}
public int Pmin { get { this.Items.Min(i => i.P); } } //concept
public int Pmax { get { this.Items.Max(i => i.P); } } //concept
}
They're being used in a templated control, whose XAML looks like this:
(adding comments to make it as clear as i'm able to)
<!-- myGridObject = new myTemplatedControl(); -->
<!-- myGridObject.DataContext = new myTypeCollection(); -->
<!-- NOTE: collection obviously is NOT empty in the real code -->
<sdk:DataGrid ItemsSource={Binding DataContext}>
<sdk:DataGridTemplateColumn Width="Auto">
<sdk:DataGridTemplateColumn.HeaderStyle>
<Style TargetType="sdk:DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- ?????? write out Ptotal in the header of the column ??????? -->
<!-- This throws a binding-related ArgumentException -->
<TextBlock Text="{Binding ???? Ptotal ?????}" />
<!-- closing tags cut off -->
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding P}" />
<!-- closing tags cut off once more-->
{Binding P} works as expected, since P is a property of the items, but how do I access the collection's properties like Ptotal, Pmin, etc. ?
Thanks for taking the time to read this. If any info is missing just point it out and I'll post it.
I think the issue is that the DataGrid is bound to the collection, and each row is bound to an individual item, not the collection. You need to gain access a level up the chain (back to the collection itself).
If you're running Silverlight 4+ you can use a relativesource. For instance:
<TextBlock Text="{Binding Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType=sdk:DataGrid, AncestorLevel=1}, Path=DataContext.Count}"
Otherwise perhaps create a static access to the context to access it via the binding Source
So you need the collection object as your binding source.
You need these:
RelativeSource MarkupExtension
Binding.RelativeSource Property
Something like this (not tested):
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type myTemplatedControl}}, Path=DataContext.Ptotal}" />
If the DataGrid is inside your custom myTemplatedControl. It not totally clear for me what is myGridObject exactly. The main idea is:
As the MSDN documentation says: Binding.RelativeSource Gets or sets the binding source by specifying its location relative to the position of the binding target.
If you stuck with the x:Type extension, here is a link about it, so you can use it with your custom control:
X:Type
Another approach is if you name your container element (where your collection is the datacontext), then you can set that element as the binding source:
<TextBlock Text="{Binding ElementName=yourElementName, Path=DataContext.Ptotal}" />
Turns out the customer changed his mind about the grid headers, he doesn't want total displayed in the header anymore.
By the way, I must have tried 20 different approached including patching in various types of converters but I haven't been able to accomplish this not-as-simple-as-it-looks-apparently task.
Thanks again for the nonetheless interesting suggestions.

Get object value from Listbox Item

Im currently developing an Windows Phone 7.0 Application with C#. I got an Listbox whichs items is based on the results of an WCF-Service. I've created my own object which contains a few values that have been fetched from an database. The problem is that i don't know how to change the content of each Listbox Item depending on the values of that items parameters that was fetched using the webservice.
Structure:
User enters the XAML Page
An connection is being opened between the client and the webservice
The webservice returns an List<Friend> The important parameter inside Friend is called Verified
The listbox items is set using: lstFriends.ItemsSource = e.Result;
What i want to do:
I want to check in each Listbox Item after the parameter Verified and check it's value.
Depending on if the parameter is true or false a TextBlock inside the Listbox Item should have different text.
Thanks
This is probably an ugly way to do it with no code-behind, but what you could do is create a template for your listboxitem that includes two textblocks (something) like this:
<Style x:Key="ListBoxItemStyle1" TargetType="ListBoxItem">
<Setter Properties... />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<TextBlock Text="Verified" Visibility={Binding Verified, Converter="{StaticResource BoolToVisibilityConverter}" />
<TextBlock Text="Not Verified" Visibility={Binding Verified, Converter="{StaticResource ReverseBoolToVisibilityConverter}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You would then have to write two converters: BoolToVisibilityConverter to change the boolean value of Verified to Visible if True and Collapsed if False for the first text block and ReverseBoolToVisibilityConverter to change the boolean value of Verified to Visible if False and Collapsed if True on the second text block. This way one textblock will always be visible in the listboxitem and one will always be collapsed, depending on the value of the Verified property.
If you don't know how to do value converters, you can look HERE.
This isn't tested and is not all of the code you'd need, but it should work. This assumes that the two different text blocks will always contain the same text and that the Verified property is a boolean property, if not then you might want to figure out another way to do it.
On second thought, you could just do ONE value converter and one textblock and convert the value of Verified to the text you want. That would be easier.
<TextBlock Text="{Binding Verified, Converter="{StaticResource VerifiedToTextConverter}" />

Categories