I'm a long time WPF designer yet new to windows app development. I'm trying to bind a collection of objects onto a grid yet keep getting the error Unknown attachable member '(Grid.Row)' on element 'FrameworkElement' and Unknown attachable member '(Grid.Column)' on element 'FrameworkElement'.
Can someone please explain to me the how to set the various Grid attached properties via style?
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<!-- Column and row definitions omitted for brevity -->
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="FrameworkElement">
<Setter Property="(Grid.Row)" Value="{Binding Row}" />
<Setter Property="(Grid.Column)" Value="{Binding Column}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Don't use a PropertyPath. All you need is a qualified Owner.Property string.
<Setter Property="Grid.Row" Value="{Binding Row}" />
Taken from PropertyPath XAML Syntax
Some style and template properties such as Setter.Property take a
qualified property name that superficially resembles a PropertyPath.
But this is not a true PropertyPath; instead it is a qualified
owner.property string format usage that is enabled by the WPF XAML
processor in combination with the type converter for
DependencyProperty.
It turns out that there are actually 3 problems with the code I posted above.
As #LPL correctly identified Setter.Value takes a qualified property name where a PropertyPath was being used. The fix here is to drop the parentheses: <Setter Property="Grid.Row" ... /> and <Setter Property="Grid.Column" ... />.
The second issue is with the style target type. It turns out that the metro Grid attached properties can't be applied to FrameworkElement's. The solution here is to update the target type with something more specific: <Style TargetType="ContentPresenter" />.
Finally as with Silverlight, the value property of metro setters don't support bindings. Consequently even after fixing the previous two errors, the setter is actually trying to set the grid attached properties to an instance of type Binding. While not as straight forward, all the details of a solution may be found here. In summary you can use the setter to set a custom attached property, which will in turn set up any desired binding.
Related
Since I'm still struggling with understanding how ItemContainerStyle works, I tried to go to the root component that defines its behavior, that is ItemsControl.
The simplest application of style I can think of is trying to apply a couple of settings, let say the Background and the Foreground to the item.
<Window.DataContext>
<local:VM></local:VM>
</Window.DataContext>
<DockPanel >
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Foreground" Value="red"/>
<Setter Property="Control.Background" Value="yellow"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Window>
The underlying class for the data is:
public class VM
{
public ObservableCollection<string> Items { get; set; } = new ObservableCollection<string>();
public VM()
{
Items.Add("first");
Items.Add("second");
Items.Add("third");
}
}
The result:
Ok, background is not applied, but this is not what I wanted to check and BTW in WPF there seem to be more exceptions than rules. (And BTW2 i've already fighted with assigning the background of a ListBox selected item, that requires to retemplate the whole thing, maybe here it's similar? If you know the answer it's appreciated, but I leave it for now because it's taking me off track).
Let's also have a look at the Visual Tree:
That is, for ItemsControl the items don't get a 'wrapper element'; if we do the same with a ListBox, for each item of the collection, it will be constructed a ListBoxItem.
Now let's try to template the item by adding this (just after </ItemsControl.ItemContainerStyle>) :
<ItemsControl.ItemTemplate>
<ItemContainerTemplate>
<Label MaxWidth="100" Content="{Binding}"/>
</ItemContainerTemplate>
</ItemsControl.ItemTemplate>
This is the result (they are moved in the center because of the MaxWidth="100"; I wanted to see if there was something behind):
The style is not applied anymore. Let's have a look at the Viusal Tree:
This visual tree is not surprising, we just replaced default representation that before was a TextBlock. In its place now we find a Label with its own standard sub-tree.
What's surprising is that at least Foreground should apply to the label too, but unfortunately it doesn't.
What's going on then?
I've read a very similar question here:
Can't use ItemTemplate and ItemContainerStyle together?
It differs from this in that it tries to assign the ContentTemplate. Since I'm still struggling with the basic behavior here (and I didn't understand the answer there except that there is some sort of copy-problem) I decided to put this more basic question.
However it seems there is a style-targeting problem here and not a copy problem; this is because if I keep the ItemTemplate, but replace the Label with a TextBlock (that leads to the very same VisualTree of the non-templated version) I get back my foreground red color!
<ItemsControl.ItemTemplate>
<ItemContainerTemplate>
<TextBlock MaxWidth="100" Text="{Binding}"/>
</ItemContainerTemplate>
</ItemsControl.ItemTemplate>
Getting warmer?
So it seems that the framework checks if the component is TextBlock and if not doesn't apply the style.
But this is the default behavior when applying implicit styles: a stile with (TargetType == the type of the control being styled).
In this case it seems like the framework assumes that the TargetType is TextBlock, and never reconsiders this assumption even if ItemTemplate is set.
In order to better understand how the style-target works here I tryed to set the style's TargetType explicitly, who knwos, so let's try this:
<ItemsControl.ItemContainerStyle>
<Style TargetType="Label">
<Setter Property="Label.Foreground" Value="red"/>
<Setter Property="Label.Background" Value="yellow"/>
</Style>
</ItemsControl.ItemContainerStyle>
See the TargetType="Label"? Great. It gives the error:
Cant apply to ContentPresenter a style intended for Label.
(translated from italian, maybe not the exact wording in english. plz replace with the exact one if you have it at hand).
That is, it expects this:
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Label.Foreground" Value="red"/>
<Setter Property="Label.Background" Value="yellow"/>
</Style>
</ItemsControl.ItemContainerStyle>
It somewhat makes sense, because the root node of each item, according to the visual tree shown before is actually ContentPresenter.
At this point I'm quite confused: how is it supposed to work? The idea for the moment is that it doesn't.
The behavior for the subclasses like ListBox seems to be more sensible: it styles the container of the item; here a container for the item doesn't exist. That's just my guess because i couldn't find any documentation saying this.
You're looking at your items and thinking about them when setting the ItemContainerStyle.
But of course this is their container you're setting a style on. The container of each item. You don't really care about your container because it's not doing much.
Maybe a concrete example of a use case would be clearer than theory.
If you look at:
https://i.imgur.com/UZ6Nqrc.png
Those red and blue rectangles are units in this game.
Those are a variety of nato symbols indicating infantry, artillery cavalry etc.
An itemcontainerstyle is used to position them.
The whole panel on the left has an itemscontrol with a canvas as it's itemspanel ( instead of the default stackpanel ).
There is a viewmodel for each unit and a collection of these is bound to the itemssource of that itemscontrol.
A unit viewmodel has an X and Y property which is used to position the unit within that canvas.
The position of a unit is defined by a point which is the centre of it's view. Glossing over exactly why that is, I think this is interesting because the unit's viewmodel doesn't need to calculate the offset from centre to top left corner. This is done by a converter in the view and applied using a style:
<Style TargetType="ContentPresenter" x:Key="CenteredContentPresenter">
<Setter Property="Canvas.Top">
<Setter.Value>
<MultiBinding Converter="{local:MultiAddConverter}">
<Binding Path="Y" Mode="TwoWay"/>
<Binding Path="ActualHeight"
Converter="{local:MultiplyConverter Multiplier=-.5}"
RelativeSource="{RelativeSource Self}"
Mode="TwoWay" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Canvas.Left">
<Setter.Value>
<MultiBinding Converter="{local:MultiAddConverter}">
<Binding Path="X" Mode="TwoWay"/>
<Binding Path="ActualWidth"
Converter="{local:MultiplyConverter Multiplier=-.5}"
RelativeSource="{RelativeSource Self}"
Mode="TwoWay" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
Elsewhere, in the map editor trees are positioned in a similar manner.
The ItemContainerStyle is applied to the item containers that gets created by the virtual GetContainerForItemOverride method.
In the ItemsControl base class, this method returns a ContentPresenter:
protected virtual DependencyObject GetContainerForItemOverride()
{
return new ContentPresenter();
}
In the derived ListBox class, it returns a ListBoxItem:
protected override DependencyObject GetContainerForItemOverride()
{
return new ListBoxItem();
}
The TargetType of the ItemContainerStyle must match the type of the DependencyObject returned from this method. Otherwise, you'll get an exception when the style is applied to the container(s) at runtime.
I'm using MaterialDesignInXaml for WPF which provides 3rd party controls and styles. I need to edit one of these styles by changing one property.
I am using an Expander control which has a template creating a bunch of child controls. I've discovered the child 'Border' control (4 layers deep) has the property (padding) which I need to set to zero.
See this output from Snoop showing the property I need to change:
Link to image
My question is how can I do this? I've tried extending the style used by the control as follows, but it isn't changing anything so I assume I'm doing something wrong?
<Style TargetType="{x:Type Expander}"
x:Key="MaterialDesignExpanderHeadless"
BasedOn="{StaticResource MaterialDesignExpander}">
<Style.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="Padding" Value="0"></Setter>
</Style>
</Style.Resources>
</Style>
I am able to use the style like this. And I know this is working for sure:
<Expander Header="Header Content" Style="{StaticResource MaterialDesignExpanderHeadless}">
Some Content
</Expander>
You're right, this method should work. Something else is setting the border's padding.
Snoop is telling you the padding is defined by the parent template, which could be the HeaderSite (ToggleButton).
You could try to extend the ToggleButton style (BasedOn) or redefine it locally.
I have a XAML file in my Xamarin project that displays different views depending on the state of 2 picker views. The Picker View is a custom view that lets you display an Enumerator as a picker. The important part is, that the SelectedItem does fire the PropertyChanged notification.
So in my Xaml I define my style like this:
<Style x:Key="SinglePressureRelativeHumidity" TargetType="ContentView">
<Style.Triggers>
<MultiTrigger TargetType="ContentView">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding Source={x:Reference Mode}, Path=SelectedItem}"
Value="{x:Static enums:HumidityCalculatorMode.SinglePressure}" />
<BindingCondition Binding="{Binding Source={x:Reference KnownValue}, Path=SelectedItem}"
Value="{x:Static enums:HumidityCalculatorKnownValue.RelativeHumidity}" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="IsVisible" Value="true" />
</MultiTrigger.Setters>
</MultiTrigger>
</Style.Triggers>
</Style>
This is placed inside the local resource dictionary. The References Mode and KnownValue refer to the custom picker views, which are correctly defined in the same xaml file.
I later have a StackLayout with multiple ContentPages each looking similar to the following:
<ContentView Style="{StaticResource SinglePressureRelativeHumidity}"
IsVisible="False">
<StackLayout>
<controls:TemperatureEntry Title="Temperature"
Temperature="{Binding HumidityCalculator.InputTemperature, Mode=TwoWay}"/>
<controls:PressureEntry Title="Test Pressure"
Pressure="{Binding HumidityCalculator.InputPressure, Mode=TwoWay}" />
</StackLayout>
</ContentView>
Where each ContentPage has its own Style with different Conditions.
Now to the problem, when I change the value of any of the picker the ContentPages get enabled or disabled as you would expect, the one where the style's MultiTrigger's Conditions are met gets set to visible all other are set to invisible.
However, the problem is that when loading the view all are set to invisible. So it is as if the trigger only checks when there are changes made by the user. I have tested various things.
First I tried setting the value of both pickers to the wanted default value after the InitializeComponent method without success. I made sure that the Property SelectedItem does fire the PropertyChanged notification with the correct name.
Second I tried inverting the isVisible property of the ContentViews to true but then all were visible which also wasn't what i wanted.
So how can I trigger the MultiTrigger with my default values?
I was able to fix this by binding the BindingCondition directly to the Model which the Pickers set their values to.
I am not sure why this fixed the issue.
I am trying to define the IsEnabled Property on each ListViewItem in the ListView based on the value of an attribute of the item itself. The xaml below will allow me to bind to a property of the page's datacontext, but not to one of the indiviual item being displayed.
<ListView Height="450" ItemsSource="{Binding ItemCollection}" ItemClick="ItemSelected" IsItemClickEnabled="True" SelectionMode="None" FontFamily="Global User Interface">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="IsEnabled" Value="{Binding MarkedOffNotRemoved}"></Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
With the property on the Item Type
public Boolean MarkedOffNotRemoved
{
get
{
return MarkOff == null || !MarkOff.Removed;
}
}
With the code like this, the error I see is from ReSharper (Not VisualSTudio) and it is a "Cannot resolve property in the Data context" error. The error also indicates the context it is looking at is the ViewModel for the page, not the individual items. VisualStudio compiles the code without error, however the page does not behave as expected and when debugging the get for MarkedOffNotRemoved is not called at all.
Is there a way to change my binding to an attribute of each item in the collection such that the binding property will actually be called? Or will I need to use an ItemContainerStyleSelector along with a style for enabled items and a style for disabled items?
i've got a silverlight (v2) datagrid where some items are section headers and as such must appear with a different background colour.
i'm trying to do this with the following xaml:
<dg:DataGrid.RowStyle>
<Style TargetType="dg:DataGridRow">
<Setter Property="Background" Value="{Binding Path=Background, Mode=OneTime}" />
</Style>
</dg:DataGrid.RowStyle>
i expect it to bind the Background property of the datagrid row viewmodel to each row's Background property, instead i get a lovely unknown xaml parsing error:
{System.Windows.Markup.XamlParseException: AG_E_RUNTIME_MANAGED_UNKNOWN_ERROR [Line: 16 Position: 57]
at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)
at Etana.Survey.Silverlight.UserInterface.Views.MaximumProbableLossPage.InitializeComponent()
at Etana.Survey.Silverlight.UserInterface.Views.MaximumProbableLossPage..ctor()}
if i try to explicitly specify "Red" and not try and bind the style, then it works, so I wonder if silverlight would allow me to bind a style like that or if there's some other trick to it.
(the xaml is based on a wpf implementation of this which works fine)
any input would be much appreciated
Change your binding to TemplateBinding. eg
<dg:DataGrid.RowStyle>
<Style TargetType="dg:DataGridRow">
<Setter Property="Background" Value="{TemplateBinding Background, Mode=OneTime}" />
</Style>
</dg:DataGrid.RowStyle>
Silverlight as of version number 4 doesn't support bindings in a Setter Value. There is a workaround implemented as an attached property:
SetterValueBindingHelper