0

I'm attempting to write a program, part of which will display a list of open windows (or more specifically, their names or titles)

So the XAML for the view looks like this:

<Window.DataContext>
  <local:ViewModel />
</Window.DataContext>
<ItemsControl ItemsSource="{Binding Windows}" />

and the ViewModel class looks like this:

public ObservableCollection<Window> Windows { get; set; }

public ViewModel()
{
  this.Windows = new ObservableCollection<Window>();
  this.Windows.Add(new Window());
}

This causes the program (and the designer view) to throw InvalidOperationException: Window must be the root of the tree. Cannot add Window as a child of Visual.

It seems that the problem is that the ItemsControl thinks I actually want to add the Window itself as a control rather than as a class (where I would expect the window to show the text System.Windows.Window or something similar).

I've tried adding <ItemsControl.ItemTemplate><DataTemplate>..., but this seems to have the same result.

Finally, I've tried creating a dummy WindowHolder class with a single public property of a Window. That seems to work, but seems a really inelegant way of doing things where it feels like it should be simpler.

tl;dr

The question is "How can you simply (preferably in XAML) display a list of window titles on a WPF ItemsControl, bound to an ObservableCollection<Window> in the view model?

Philip C
  • 1,819
  • 28
  • 50
  • I suspect you've already got the best approach - it sounds like WPF doesn't like populating an `ItemsControl` with a ref to an element which already exists further up the visual tree (I can imagine that some craziness could result if you pointed to the `Content` property of the `Window` objects in that collection if this was allowed...think infinitely recursive visual tree!). The simplest solution is to create a wrapper which you've already done. – Charleh Sep 05 '13 at 14:50
  • @Charleh You might want to put that down as an answer. If no-one comes back with an alternative view I'll mark yours as the correct one. – Philip C Sep 05 '13 at 14:53
  • Did you just want the window titles ? Or an actual Window – 123 456 789 0 Sep 05 '13 at 14:59
  • @LeoLorenzoLuis I just want to display some property/properties of the window. Things like the Name, Title, Dimensions, for example. I don't want to try to display the actual window contents themselves. – Philip C Sep 05 '13 at 15:00
  • @PhilipC If you just want to display properties of a Window, why do you need a Window Control for? – 123 456 789 0 Sep 05 '13 at 15:32
  • @LeoLorenzoLuis It's not that I *need* a Window control, it's that I have a collection of Window controls and I want to show some of their properties in a list. – Philip C Sep 05 '13 at 15:38
  • Can you not just create a Model (WindowProperties.cs) that wraps the Window's properties that you need and don't use that collection of Windows as your ItemsSource but instead the Collection of a Model with the properties that you want to display and do the binding. – 123 456 789 0 Sep 05 '13 at 17:16
  • Since you don't need to show the Window at all, you might not see right now the performance hit you are going to get in the future once the collection gets bigger. – 123 456 789 0 Sep 05 '13 at 17:18

4 Answers4

2

You can use a ListBox instead of ItemsControl

<ListBox ItemsSource="{Binding Windows}">
    <ListBox.Resources>
        <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
        <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" />
        <SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />
    </ListBox.Resources>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Title}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

The issue is that the default behavior for adding controls into a generic ItemsControl causes the ContentPresenter to be wrapped in the control itself. So your collection Windows will want to be added as containers to the ItemsControl's ItemsPresenter, but it fails for the reason described in the exception. ListBox works because the wrapping containers are ListBoxItems, not Windows. In order to provide support for Window in an ItemsControl you would have to implement your own custom control for ItemsControl to return a container that isn't a Window.

jamesSampica
  • 12,230
  • 3
  • 63
  • 85
  • This does make the problem I'm having go away, and I don't think having them selectable should cause any real problems. It seems strange that `ListBox` can cope where `ItemsControl` can't... – Philip C Sep 05 '13 at 15:15
  • @Philip You can set the style to do this, I'll add the code from [this SO question](http://stackoverflow.com/questions/4343793/how-to-disable-highlighting-on-listbox-but-keep-selection) – jamesSampica Sep 05 '13 at 15:29
  • That seems to work perfectly, thank you very much. I'll leave the question open for a few days to see if any other ideas come in, but at the minute I think you're winning :P – Philip C Sep 05 '13 at 15:36
  • This must be a control template difference since `ListBox` inherits `ItemsControl` - it would be interesting to see the difference in implementation between the two. Good knowledge to have though! – Charleh Sep 05 '13 at 15:46
  • @Philip See my updated answer as the reason why you can't use a generic ItemsControl for this problem. – jamesSampica Sep 05 '13 at 17:32
1

I suspect you've already got the best approach - it sounds like WPF doesn't like populating an ItemsControl with a reference to an element which already exists further up the visual tree.

The visual tree must check references, because I've tested this by creating a wrapper class which has a ref to the Window and then used an ItemsControl to display the content of that window (to see if I could make it explode!) and it gave the following error:

Logical tree depth exceeded while traversing the tree. This could indicate a cycle in the tree.

Which is an infinitely recursive tree

So in summary the simplest solution seems to be to create a wrapper for the properties that you want (which you've already done) and avoid referencing ancestor elements in leaf/child nodes!

Charleh
  • 13,749
  • 3
  • 37
  • 57
0

Your thinking is correct, you need to create an ItemTemplate for the ItemsControl. Each item in the ItemsControl will be of Type Window and you need to expose the Title property in the template

<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text={Binding Title}/>
</DataTemplate>
</ItemsControl.ItemTemplate>
Krishna
  • 1,956
  • 13
  • 25
0

Well what I would do is the following:

  • Create an Interface wrapper(as already stated) for window ie IWindow with Title property.
  • All Windows would implement IWindow
  • Make an Observable collection of IWindow instead of Window.
sevdalone
  • 390
  • 1
  • 9
  • I'd suspect this would have the same issue as technically a ref to `IWindow` would still be a ref to `Window` and I've got a feeling that the visual tree would know! Worth a try though – Charleh Sep 05 '13 at 15:34
  • Just tested (this new laptop is FAST!), and as I suspected; this doesn't work. The `IWindow` is still really a ref to `Window` and the visual tree knows this :( – Charleh Sep 05 '13 at 15:37