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?
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.
Background:
I have a Datagrid with some Measurements and this Measurements we can Approve and Block.
Now we have for this a new Type, like "Cancelled". But this Type is only needed by Server and for displaying it to Customer.
But the Customer should not be able to select this "Cancelled" but the other 2 Types he should have to select.
The List get all different elements from Database (3 entries).
Firstly i tried to remove the Cancelled Item from the ApprovementCollection, but then it displayed a empty field instead of "Cancelled".
Question:
Is it possible, to disable only one of this 3 Items in the Bounded List of the Itemsource Property from the DataGridComboBoxColumn?
Disabled or Not Displayed in the Selection Menu is that what i have to do.
(Only "Freigabe" and "GESPERRT")
View:
<DataGridComboBoxColumn ClipboardContentBinding="{x:Null}"
DisplayMemberPath="ApprovementText"
Header="{x:Static trans:Translations.ClearenceHeader}"
ItemsSource="{Binding Source={StaticResource PossibleComponentMeasurementApprovements}}"
SelectedItemBinding="{Binding Approvement,
UpdateSourceTrigger=PropertyChanged}" />
Viewmodel:
private IEnumerable<ComponentMeasurementApprovement> possibleComponentMeasurementApprovements;
public IEnumerable<ComponentMeasurementApprovement> PossibleComponentMeasurementApprovements {
get { return possibleComponentMeasurementApprovements; }
set {
possibleComponentMeasurementApprovements = value;
OnPropertyChanged();
}
}
Thanks for your Help
This is possible writing a DataGridTemplateColumn for your cell instead of using the DataGridComboBoxColumn. Just add a property Enabled to your ComponentMeasurementApprovement class. This property indicates if the user is allowed to select the approvement.
Now create a new DataGridTemplateColumn containing a ComboBox as template. It is possible to bind IsEnabled of every ComboBox item to a proeprty by styling them via ItemContainerStyle.
Here is the code:
<DataGridTemplateColumn Header="CustomCell">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={x:Static local:ViewModel.PossibleComponentMeasurementApprovements}}"
DisplayMemberPath="ApprovementText"
SelectedItem="{Binding Approvement}">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsEnabled" Value="{Binding Enabled}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Here is the result:
Since the second item is disabled, it's not possible to select it but an already selected item keeps selected.
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.
I'm struggling for about 14 days now with a simple task: In database, I have definitions for hardware categories. For example :
HDD
Internal
External
Flash
This list is in database defined like this:
[ID - ParrentID - Name] : 1 - 0 - HDD, 2 - 1 - Internal, 3 - 1 - External, 4 - 1 - Flash.
Through Entity Framework I get these rows into my application. From this flat data I then create structured object which is my DataModel. This model is defined as follows :
public class Category
{
private int _id = -1;
private string _name = "";
private List<Category> _subCategories = null;
// property getters and setters, constructors, and bool HasSubCategories
}
Now, from these I create ViewModel called SubCategoryViewModel to which is binded my TreeView. So, I can view my categories in treeview and with my defined and maintained hierarchy. This works just fine. In SubCategoryViewModel is defined a Command through Attached Behavior for MouseDoubleClick which is also binded to TreeView. So, when user doubleclicks on Item, in SubViewCategoryModel defined method will execute particular code. List of SubCategoryViewModel is nested in HWDocumentViewModel which is a main ViewModel for my window.
What I need now is obvious : When user doubleclicks on item in TreeView, I need to load items from database and show them in ListView. My opinion is, that in HWDocumentViewModel I need to define an collection of Items and load them accordingly to selected category in ListView. But, I don't know how to execute a method on HWDocumentViewModel from SubCategoryViewModel. Because : TreeView is binded to list of SubCategoryViewModel items, so when DoubleClick occurs, the method on SubCategoryViewModel is executed. I'm searching for a way, how to execute a method on main ViewModel (HWDocumentViewModel).
I tried this approach :
I created a property : public static SubCategoryViewModel SelectedCategory on HWDocumentViewModel. When doubleclick occurs, I set this property from SubCategoryViewModel as this. So, in this property is object, which executed doubleclick event delegate. Great, now I have in HWDocumentView model an object, which user selected.
So, I need to load items to ListView. But, will I load them from method in SubCategoryViewModel ? I don't think so. Instead I should load them from Main View Model by creating a ViewModel for them and bind it to ListView, right ? But, how can I from SubCategoryViewModel call a method in HWDocumentViewModel ? Should I write a static method
on a HWDocumentViewModel which will be accessible from SubCategoryViewModel ?
Or is there a way, how to call Command defined on HWDocumentViewModel from SubCategoryViewModel ?
Or generally, did I take a right approach to create a Warehouse-like application in WPF ?
Thanks a lot.
EDIT: XAML for my TreeView looks like this :
<TreeView x:Name="tvCategories" Background="White" ItemsSource="{Binding Categories}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="behaviors:MouseDoubleClick.Command" Value="{Binding MouseDoubleClickCommand}" />
<Setter Property="behaviors:MouseDoubleClick.CommandParameter" Value="{Binding}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type localvm:SubCategoryViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding CategoryName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I'm not sure I see the problem. You have a tree of subcategories and when one is selected, the appropriate SubCategoryViewModel sets itself as SelectedCategory on the main HWDocumentViewModel. That seems like a reasonable approach.
So why do you need to call a command? Why can't you just load the new list in HWDocumentViewModel in response to a change of its SelectedCategory property (ie in the setter)?
If you really must use a command to invoke the load, then simply keep a reference to your main HWDocumentViewModel in each SubCategoryViewModel, and invoke the command with a simple:
_mainViewModel.LoadCategoryCommand.Execute();
With MVVM and trying to communicate between View and ViewModel or between ViewModels a publisher/Subscriber setup works well or a messaging paradigm like what's found in MVVMLight or Prism. I posted an answer on MVVM Light's messaging setup here
In the message you can send an object that holds any data you would like to send back and forth between the view models.
I highly recomend using a framework when working with mvvm it makes like much easier. MVVM Framework Comparison is a link to an answer that goes through a comparison of some of the major frameworks.
I have a control which has a default value for a property. When the control first gets its dataContext set, it assigns this property automatically.
In the xaml now, I want it to be possible to UNset this property. I've tried setting it to x:Null of just the empty string, but then I get an error because there's no converter for the property. How do I simply unassign this property from the xaml in the rare cases where I want the feature disabled?
code where it is originally set:
void OmniBox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if( e.NewValue is BindingObjectBaseExtended )
{
BindingObjectBaseExtended value = (BindingObjectBaseExtended)e.NewValue;
this.SetBinding(OmniBox.ContextValidationMessagesProperty, new Binding() { Source = value, Path = new PropertyPath("ValidationMessages") });
}
}
xaml where I want to unset the property.
<Style TargetType="ui:OmniBox">
<Setter Property="ContextValidationMessages" Value="" />
</Style>
Note that if I do not set up the binding automatically when the data context changes, then by default there are no validation messages and I have to do the following in the xaml to set them up:
<Style TargetType="ui:OmniBox">
<Setter Property="ContextValidationMessages" Value="ValidationMessages" />
</Style>
What I'm trying to do is make the above binding the default for my custom OmniBox control, and allow the user to unset it or change it to something else.
DependencyProperty.UnsetValue cannot be used in XAML.
http://msdn.microsoft.com/en-us/library/system.windows.dependencyproperty.unsetvalue(v=vs.90).ASPX
Personally, I would create a separate dependency property, such as bool AutoBindValidation and make it default to true. If it is false, don't do anything when the DataContext changes. This is a little more self-documenting. Depending on what exactly you're trying to do, you might not want to publicly expose ContextValidationMessages at all.
If you really want to do it the way you posted, I'm not sure why setting it to {x:Null} would cause an error (unless the property type is not nullable). But this approach would have problems because DataContextChanged is going to occur after the XAML is parsed. So the user can set it to {x:Null}, but then the DataContext will change and your code will set up the default binding and trample the user's value. You could set up the binding in the control's contstructor, but then if the DataContext does not have a ValidationMessages property, your control will be spitting out binding errors.
This may be impossible, my best bet was this:
<Setter Property="ContextValidationMessages"
Value="{x:Static DependencyProperty.UnsetValue}" />
But that throws "Cannot unset setter value". So you better inverse your logic or keep the property unset another way.
I don't think there is any supported way to do this in the xaml itself. In your code you are setting a local value on the ContextValidationMessagesProperty. The Style setters you included would have a lower dependency property precedence and even if they were evaluated they would set a value based on the specified Value - not clear it. Maybe instead of setting the binding in code you could have a Setter in your default style for OmniBox that sets that property - e.g.
<Setter Property="ContextValidationMessages" Value="{Binding ValidationMessages}" />
If you have to conditionally set the Binding then you could create a custom IValueConverter that checks for the specified type (passed as the parameter). e.g.
public class IsAssignableFromConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Type typeParameter = parameter as Type;
if (typeParameter == null)
return DependencyProperty.UnsetValue;
return value != null && typeParameter.IsAssignableFrom(value.GetType());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
Then you might use it like this:
<local:IsAssignableFromConverter x:Key="isAssignableConverter" />
<Style TargetType="ui:OmniBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource isAssignableConverter}, ConverterParameter={x:Type ui:BindingObjectBaseExtended}}" Value="True">
<Setter Property="ContextValidationMessages" Value="{Binding ValidationMessages}" />
</DataTrigger>
</Style.Triggers>
</Style>
In the case where you don't want this property to be applied you might set the Style for that instance of the OmniBox to a new style and make sure to set the OverridesDefaultStyle property to true.
I suppose another option is to create another dependency property that will call ClearValue on the ContextValidationMessages property but this seems like it could be a maintenance issue.
For certain cases you can 'reset' to the default value of the parent control by using a RelativeSource. For instance I'm using a DataGrid and this worked for me to reset back to the 'default'.
This is a textblock inside a datagrid cell.
<TextBlock Text="{Binding ServiceName}">
<TextBlock.Style>
<Style>
<Style.Triggers>
<!-- Change text color to purple for FedEx -->
<Trigger Property="TextBlock.Text" Value="FedEx">
<Setter Property="TextBlock.Foreground" Value="Purple"/>
</Trigger>
<!-- Reset if the cell is selected, since purple on blue is illegible -->
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}}" Value="True">
<Setter Property="TextBlock.Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
This seems clever enough to inherit the correct color even when the window is inactive.