1

I have this ListView

<ListView ItemsSource="{Binding Items}">
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="Background" Value="{Binding IsValid, Converter={StaticResource BooleanToBrushConverter}" />
        </Style>
    </ListView.ItemContainerStyle>
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Name}" />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

I know bindings don't work for Setters in Universal Applications but, then how do I bind a the container of an item with the item itself? What's the point of creating a custom container if you cannot provide any logic, but constant values?

halfer
  • 19,824
  • 17
  • 99
  • 186
SuperJMN
  • 13,110
  • 16
  • 86
  • 185
  • 1
    I have always only used {Binding ObjectProperty} in the DataTemplate. As a workaround, you could simply throw a border around your TextBlock inside of the DataTemplate with the same Background declaration. Do you see any BindingExpression errors in your Output window when you run the application? Does it hit a breakpoint? – Mark W Oct 12 '15 at 12:47
  • @MarkW Bindings in Style Setters won't work in Windows Runtime/UWP. [OP is aware of that fact](http://stackoverflow.com/q/33052081/1136211). – Clemens Oct 12 '15 at 12:59
  • @clemens OP is aware of a problem and nobody provided a solution. I've created a custom container and there's no way to bind to it. It's so common in WPF that I thought there was a solution. No FindAncestor and no Binding it Setters. I can't believe that such a normal scenario is impossible to solve. – SuperJMN Oct 12 '15 at 13:42
  • @Clemens That ONLY works for a "predefined" property: IsSelected. Do you think that creating an attached property for each property you want to bind is "a solution" or a "**bad solution**"? Do you mark as solved something which only has a bad solution? It's all about quality, not quantity. – SuperJMN Oct 12 '15 at 14:40

3 Answers3

1

You need to be careful with backgrounds in the UWP ListViewItem, as there is a lot of complex theming around this including different kinds of pressed and drag backgrounds

I think an easier way to achieve this is to change the content alignments in the ListViewItem, and then add a grid to your item template from which you can add your background

<ListView ItemsSource="{Binding Items}">
<ListView.ItemContainerStyle>
    <Style TargetType="ListViewItem">
        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
        <Setter Property="VerticalContentAlignment" Value="Stretch"/>
    </Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
    <DataTemplate>
        <Grid Background="{Binding Path=IsValid, Converter={StaticResource BooleanToBrushConverter}}">
            <TextBlock Text="{Binding Name}" VerticalAlignment="Center" />
        </Grid>
    </DataTemplate>
</ListView.ItemTemplate>

Dean Chalk
  • 20,076
  • 6
  • 59
  • 90
  • Thanks, Dean, but what if I had my own ItemContainer with a custom property? From the DataTemplate I don't have any way to bind to the ItemContainer. – SuperJMN Oct 12 '15 at 20:19
  • It works but there are a margin between the item border and the Grid. Is there a way to solve it? – kokokok Jul 15 '16 at 11:52
1

OK, you can't bind to the DataContext using the normal ways, but you can do it using other (smart) methods:

See this post. It provides a "helper" that allows to bind to the DataContext with a Setter.

halfer
  • 19,824
  • 17
  • 99
  • 186
SuperJMN
  • 13,110
  • 16
  • 86
  • 185
-1

This is pretty easy, really.

I would guess you are trying to use the variable sized grid view? That's a pretty common request, actually. But what you are asking for is tricky because of the various scopes and how things are rendered.

The first think you will need to do is override ListView with your own custom ListView. Let's call it MyListView. Like this:

public class MyItem
{
    public int RowSpan { get; set; }
    public int ColSpan { get; set; }
}

public class MyListView : ListView
{
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        var model = item as MyItem;
        try
        {
            element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, model.ColSpan);
            element.SetValue(VariableSizedWrapGrid.RowSpanProperty, model.RowSpan);
        }
        catch
        {
            element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, 1);
            element.SetValue(VariableSizedWrapGrid.RowSpanProperty, 1);
        }
        finally
        {
            element.SetValue(VerticalContentAlignmentProperty, VerticalAlignment.Stretch);
            element.SetValue(HorizontalContentAlignmentProperty, HorizontalAlignment.Stretch);
            base.PrepareContainerForItemOverride(element, item);
        }
    }
}

Everything takes place in PrepareContainerForItemOverride and it's the only method you need override in the subclass. Please also notice that I have not set them to a binding. This is because these properties are only observed when the item is rendered. If you want to refresh your ListView and re-render your items based on new values, you need to call InvalidateMeasure() on the root panel, which is tricky. You can do it like this:

// MyListView

public void Update()
{
    if (!(this.ItemsPanelRoot is VariableSizedWrapGrid))
        throw new ArgumentException("ItemsPanel is not VariableSizedWrapGrid");

    foreach (var container in this.ItemsPanelRoot.Children.Cast<GridViewItem>())
    {
        var model = item as MyItem;
        VariableSizedWrapGrid.SetRowSpan(container, data.RowSpan);
        VariableSizedWrapGrid.SetColumnSpan(container, data.ColSpan);
    }

    this.ItemsPanelRoot.InvalidateMeasure();
}

If this makes sense, you can see the entire implementation here.

Best of luck!

Jerry Nixon
  • 31,313
  • 14
  • 117
  • 233