Accessing Property that has a setter inside a template - wpf

I have a canvas who takes many type of object and shows them in specific locations.
Most of the objects has a Location property which is set by a “Setter” and it works great.
I have one complex object – assembled from 4 bitmaps.
I want to be able to set each bitmap location using the canvas.top and canvas.left but not with the location in the setter (they don’t have it) but using their own location property.
Here is the code.
My problem is that WPF completely ignores my canvas.top and canvas.left assignment, even if I use numbers and not binding – nothing happens as well.
What might be the problem ?
Here is my current code.
The ComplexBitmap draws itself in 0,0 and not where expected.
<ItemsControl Name="itemtest" ItemsSource="{Binding}" Height="576" Background="#FFB0B4B4" Width="704">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<local2:DragCanvas x:Name="canvas1" AllowDragging="true" AllowDragOutOfView="False" Width="704" Height="576" Background="#3AC2DED4"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=Location.X, Mode=TwoWay}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=Location.Y, Mode=TwoWay}"/>
<Setter Property ="Visibility" Value="{Binding Path= ShowOnDemo, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplateSelector>
<local:CustomTemplateSelector>
.
<local:CustomTemplateSelector.BitmapTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Path=Bitmaps/myBitmap}" Canvas.Left="{Binding Path=Location.X, Mode=TwoWay}" Canvas.Top="{Binding Path=Location.Y, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</local:CustomTemplateSelector.BitmapTemplate>
.
<local:CustomTemplateSelector.ComplexBitmapTemplate >
<DataTemplate>
<Grid>
<StackPanel>
<Image Source="{Binding Path=TopLeftBitmapObj.myBitmap}" Canvas.Left="400" Canvas.Top="400" /> <-- Here I've tried to bind to another property, but even the 400 didn't work -->
</StackPanel>
<-- Here comes 3 more bitmaps -->
</Grid>
</DataTemplate>
</local:CustomTemplateSelector.ComplexBitmapTemplate>
</local:CustomTemplateSelector>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>

Please check the following:
Run in visual studio in debug mode and check for path error (As Raptor sugested)
Remove
the Style from the content presenter (As you said, ComplexBitmap
does not contains Location property...)

Related

Navigating between data in DataGrid WPF XAML

I have checkboxes that represent the modules in my program that a user can access.
You can see from the DataGrid that the modules in the fourth column are created from "DataContext.ModuleCollection", where as the rows for the DataGrid are created from UserCollection. I would like the User_ID to be the content for each checkbox. This will be set in the checkbox style which I have attempted at the bottom without much success.
My issue is, the checkbox is at Module level (in the data) and I need to figure a way to get back to the User level to get the User_ID.
User_ID is part of the row's dataset, e.g. a User record.
How do I get User_ID in the below style is the question?
Below is my datagrid:
<DataGrid ItemsSource="{Binding UserCollection}" >
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name}"/>
<DataGridTextColumn Header="Job Title" Binding="{Binding Path=Job_Title}"/>
<DataGridTextColumn Header="Company" Binding="{Binding Path=Company}"/>
<DataGridTemplateColumn CanUserReorder="True">
<DataGridTemplateColumn.Header>
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ModuleCollection}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Background="Transparent" CanVerticallyScroll="False" CanHorizontallyScroll="False"></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Style="{StaticResource ListViewItemRotatedText}" Text="{Binding ModuleName}" >
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ModuleCollection}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Background="Transparent" CanVerticallyScroll="False" CanHorizontallyScroll="False"></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Style="{StaticResource ListViewItemCheckbox}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Below is my check style:
<Style x:Key="ListViewItemCheckbox" TargetType="CheckBox">
<Setter Property="Content" Value="{Binding Path=User_ID, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}"/>
</Style>
Your current Binding Path won't work because you're trying to access the User_ID property from the DataGridRow, which of course, doesn't have one... you should have an error in your Output Window in Visual Studio... pay close attention to these errors, as they'll help you fix your problems.
So what Binding Path can you use? Well, you're right as far as the fact that we have to use a RelativeSource Binding... try this instead:
<Style x:Key="ListViewItemCheckbox" TargetType="CheckBox">
<Setter Property="Content" Value="{Binding Path=DataContext.User_ID,
RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}" />
</Style>
What we're attempting to do here is to data bind to the User_ID value that should be found in the object set as the DataContext of the DataGridRow.
UPDATE >>>
Ahhh yeah, what I was thinking wouldn't work anyway as I was trying to bind to the whole collection and you need to bind to the current item. You're only getting these horrible problems though because of the way that you've organised (or disorganised) your data model. Those CheckBoxes should be data bound from one or more properties that are actually in your User class and not in this separate collection. I'd fix your model now before you get worse problems.
UPDATE 2 >>>
You now want to know how to bind to the whole data object rather than a property from that object. For future reference, please see the PropertyPath XAML Syntax page on MSDN for more information. However, you just need to think about this... if DataContext.User_ID gets you the User_ID property value of a User object, what do you think would return the whole User object? That's right... just remove the property:
<Style x:Key="ListViewItemCheckbox" TargetType="CheckBox">
<Setter Property="Content" Value="{Binding Path=DataContext,
RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}" />
</Style>

Binding an attached property to an item in ItemsControl with custom panel problem

I can't get the following XAML to work as I want. All bindings work because get no errors from the bindings. But I don't get the expected result from the binding on the RatioShape rectangle. The problem is that the attached property wpflib:RatioPanel.Ratio always returns its default value, not the databound value.
So I'm thinking that the attached property on RatioShape is set in the wrong "context". How do bind to the attached property so that wpflib:RatioPanel gets the bound value?
<wpflib:RatioContentPresenter2 RatioMaxValue="{Binding Path=RatioMaxValue}">
<ItemsControl Grid.Row="0" wpflib:RatioContentPresenter2.RatioOffset="{Binding Path=RatioOffset}" wpflib:RatioContentPresenter2.RatioValue="{Binding Path=RatioValue}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<wpflib:RatioPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle x:Name="RatioShape" wpflib:RatioPanel.Ratio="{Binding Path=Value}" Fill="{Binding Path=Brush}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsSource>
<Binding Path="RatioItems" Mode="OneWay" />
</ItemsControl.ItemsSource>
</ItemsControl>
</wpflib:RatioContentPresenter2>
The children of the RatioPanel will be instances of ContentPresenter, assuming the items are not UIElements. The ContentPresenter will display the DataTemplate you have defined in ItemTemplate.
Normally, panels work directly with their children. You are setting your attached property on the a child of the ContentPresenter, which is a child of your panel. I believe you should be setting this on the ContentPresenter directly. So something like this:
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="wpflib:RatioPanel.Ratio" Value="{Binding Path=Value}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle x:Name="RatioShape" Fill="{Binding Path=Brush}" />
</DataTemplate>
</ItemsControl.ItemTemplate>

How can I trigger this Error Template?

Below is a template that works from a binding perspective, but the error template doesn't show, and without an AdornedElementPlaceholder the result looks a bit garish.
My view models implement IDataErrorInfo, and normally I would trigger the error template by having ValidatesOnError=True as part of my binding. This particular view model is display only, so the IDataErrorInfo indexer is never invoked. I do have a number of useful properties related to validation though, including a boolean IsValid property as well as IDataErrorInfo.Error, both of which properly respond to the view model being invalid.
Should I translate the error to a ValidationResult and trigger it that way? Or is there something easier?
Cheers,
Berryl
current template
<!-- FooterViewModel DataTemplate -->
<DataTemplate DataType="{x:Type model:FooterViewModel}">
<Label x:Name="lblTotalTime"
Style="{StaticResource FooterStyle}"
Content="{Binding TotalTime, Converter={StaticResource TotalAmountConv}}" >
<Label.ToolTip>
<TextBlock Text="{Binding FeedbackMessage}" ></TextBlock>
</Label.ToolTip>
<Validation.ErrorTemplate>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right" Text=" *"
Foreground="Red"
FontWeight="Bold" FontSize="16"
/>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder Name="placeholder"></AdornedElementPlaceholder>
</Border>
</DockPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</Label>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsValid}" Value="False">
<Setter TargetName="lblTotalTime" Property="Control.BorderBrush" Value="Red"/>
<Setter TargetName="lblTotalTime" Property="Control.BorderThickness" Value="1"/>
<Setter TargetName="lblTotalTime" Property="Control.Background" Value="LightYellow"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
UPDATE
Ok, I am getting IDataErrorInfo to kick in just by changing my binding to include ValidatesOnErrors, BUT the error template still does not show up.
Here is the binding
<ItemsControl
ItemsSource="{Binding Path=FooterViewModels, Mode=OneWay, ValidatesOnDataErrors=True}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
By default, the validation is only run when the Source of the binding is updated. In your ItemsControl.ItemsSource binding the Sources is your FooterViewsModels, which obviously will never be updated (because you have Mode=OneWay).
You can use the DataErrorValidationRule.ValidatesOnTargetUpdated to run the validation when the target is updated as well. The link gives an example.
Keep in mind that the Binding.ValidatesOnDataErrors property is is just a short cut for adding an instance of DataErrorValidationRule to the Binding.ValidationRules collection.
Finally, the control that the binding is defined one will have the Validation.Errors. In your case, that is the ItemsControl, not the items inside it. So, I believe you need to add the DataErrorValidationRule to your Label.Content binding. Or you need to define your ErrorTemplate on the ItemsControl, depending on what you are going for.

ContextMenu.PlacementTarget is not getting set, no idea why

<DataTemplate x:Key="_ItemTemplateA">
<Grid Tag="{Binding Path=DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource ContentTemplateB}" Grid.Row="0" />
<ContentControl Name="uiContentPresenter" Content="{Binding ContentView}" Grid.Row="1" Height="0" />
<ContentControl DataContext="{Binding IsContentDisplayed}" DataContextChanged="IsDisplayed_Changed" Visibility="Collapsed" />
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Text"
Command="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
CommandParameter="{Binding}" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
The above data template is applied to an ItemsControl. The issue is that for the ContextMenu that is specified for the Grid, the PlacementTarget property is never actually getting set to anything so I cannot get to the Tag property of the Grid which is necessary for passing the Command that should execute on the parent UserControl down to the context menu. I've based this approach off of similar examples such as this: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/0244fbb0-fd5f-4a03-bd7b-978d7cbe1be3/
I've not been able to identify any other good way to pass this command down. This is setup this way because we are using an MVVM approach so the command we have to execute lives in the View Model of the user control this template is applied in. I've tried explicitly setting the PlacementTarget in a few different ways but it still always shows up as not set.
I realise that this is old and answered, but it doesn't seem properly answered. I came across a similar post and left a full answer. You might like to take a look as you can get it working with just a few adjustments to your code.
First, name your view UserControl... I generally name all of mine This for simplicity. Then remembering that our view model is bound to the DataContext of the UserControl, we can bind to the view model using {Binding DataContext, ElementName=This}.
So now we can bind to the view model, we have to connect that with the ContextMenu.DataContext. I use the Tag property of the object with the ContextMenu (the PlacementTarget) as that connection, in this example, a Grid:
<DataTemplate x:Key="YourTemplate" DataType="{x:Type DataTypes:YourDataType}">
<Grid ContextMenu="{StaticResource Menu}" Tag="{Binding DataContext,
ElementName=This}">
...
</Grid>
</DataTemplate>
We can then access the view model properties and commands in the ContextMenu by binding the ContextMenu.DataContext property to the PlacementTarget.Tag property (of the Grid in our example):
<ContextMenu x:Key="Menu" DataContext="{Binding PlacementTarget.Tag, RelativeSource=
{RelativeSource Self}}">
<MenuItem Header="Delete" Command="{Binding DeleteFile}" CommandParameter=
"{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource
AncestorType=ContextMenu}}" CommandTarget="{Binding PlacementTarget,
RelativeSource={RelativeSource Self}}" />
</ContextMenu>
Note the binding on the MenuItem.CommandTarget property. Setting this ensures that the target element on which the specified command is raised is the PlacementTarget, or the Grid in this case.
Also note the CommandParameter binding. This binds to the DataContext of the PlacementTarget, or the Grid in this case. The DataContext of the Grid will be inherited from the DataTemplate and so your data item is now bound to the object parameter in your Command if you're using some implementation of the ICommand interface:
public bool CanExecuteDeleteFileCommand(object parameter)
{
return ((YourDataType)parameter).IsInvalid;
}
public void ExecuteDeleteFileCommand(object parameter)
{
Delete((YourDataType)parameter);
}
Or if you are using some kind of RelayCommand delegates directly in your view model:
public ICommand Remove
{
get
{
return new ActionCommand(execute => Delete((YourDataType)execute),
canExecute => return ((YourDataType)canExecute).IsInvalid);
}
}
We have the same problem, but it works randomly. A contextmenu inside the controltemplate in a style for a listbox. We have tried to move the contextmenu to different levels inside the template but the same error occurs.
We think it might be connected to the refreshing of our ICollectionView that is the itemssource of the ListBox.
It seems that when the view refreshes the relative source binding inside the contextmenu is being evaluated before the PlacementTarget is being set.
It feels like a bug in either collectionviewsource or the ContextMenu of WPF...
Here is a working standalone XAML-only example based on your test case: a ContextMenu that retrieves a Command from the DataContext of its PlacementTarget using a Tag. You can reintroduce portions of your code until it stops working to try to find where the problem is:
<Grid>
<Grid.Resources>
<PointCollection x:Key="sampleData">
<Point X="10" Y="20"/>
<Point X="30" Y="40"/>
</PointCollection>
<DataTemplate x:Key="_ItemTemplateA">
<Grid Tag="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DockPanel}}}">
<TextBlock Text="{Binding X}"/>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding}"/>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
</Grid.Resources>
<DockPanel DataContext="{x:Static ApplicationCommands.Open}">
<ListBox ItemTemplate="{StaticResource _ItemTemplateA}" ItemsSource="{StaticResource sampleData}"/>
</DockPanel>
</Grid>
In this post,
ContextMenu.PlacementTarget is filled up from ContextMenuService.PlacementTarget when you do right click with mouse on button.
It means ContextMenu.PlacementTarget is filled up when the menu is shown up. You can check that by snoop.
EDIT 1
This code works fine.
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" BasedOn="{StaticResource DataGridRowBaseStyle}">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=DataContext}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu Style="{StaticResource ContextMenuStyle}"
ItemContainerStyle="{StaticResource MenuItemStyle}">
<MenuItem Header="Enable" Command="{Binding Path=PlacementTarget.Tag.(viewModels:PrinterListPageViewModel.EnableCommand), RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>

When using ItemsControl ItemsControl.ItemsPanel is set to Canvas, ContenPresenter comes in and break my Canvas properties on the children [WPF]

I am using an ItemsControl where the ItemsPanel is set to Canvas (see this question for more background information).
The ItemsControl is performing as I want, and it works like a charm when adding a child element manually by putting it into ItemsControl.Items:
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
<Button Canvas.Left="500" Content="Button Text" />
</ItemsControl.Items>
</ItemsControl>
Note the Canvas.Left property on the Button. This works like a charm, and the Button is placed 500 pixels from the left of the ItemsControl left side. Great!
However, When I am defining a ItemsSource binding to a List, the Canvas.left doesn't have any effect:
<ItemsControl ItemsSource="{Binding Elements}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Canvas.Left="500" Content="Button Text" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
By inspecting the application during run time, I see one difference. The container ContentPresenter has been added between the Canvas and the button..
How can I set the Canvas.Left property on the ContentPresenter itself? Or is there another way to solve this problem?
Thanks to all!
It is possible to set the Canvas.Left property using ItemContainerStyle:
<ItemsControl ItemsSource="{Binding Elements}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="Button Text" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="500" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
there are several solutions coming to my mind:
use a layout/rendertransform instead of the attached property
use margin instead of the attached property
derive from ItemsControl, and override the behavior how the child containers are generated. (GetContainerForItemOverride, IsItemItsOwnContainerOverride). This article is explaining quite nicely how it works: http://drwpf.com/blog/2008/07/20/itemscontrol-g-is-for-generator/
Button Doesn't have a "Canvas" Property, so what you are doing is is making a relative call to the hosting control, however because the item and canvas are in different Templates there is no direct link, because of this the Canvas.Left is meaningless before runtime.
hence your method can't find a left to set so loses the change.
However Setters are only implemented at runtime so
<Setter Property="Canvas.Left" Value="500" />
will only run after the objects have been generated and hence do have a relative relationship.
Otherwise you can use the margin which does belong to the button object but again is only interpreted at runtime

Resources