ItemsControl and controls attached properties - wpf

I'm trying to implement a diagram with movable/resizeable parts in WPF.
I would like to use ItemsControl with ItemsPanel configured to be "DynamicCanvas".
All you need to know about DynamicCanvas right now is that it acts like a usual canvas - with one exception - it utilizes attached properties to store information about X,Y attributes on its children.
My code:
<ItemsControl IsTabStop="False" ItemsSource="{Binding ElementName=comboBox1,Path=SelectedItem.Source.Table}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<s:TableControl Table="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!--<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">-->
<c:DynamicCanvas SizeHeightToContent="True" SizeWidthToContent="True" ClipToBounds="True" SnapsToDevicePixels="True" PreviewMouseDown="Canvas_MouseDown" IsHitTestVisible="True" Background="Gray" >
</c:DynamicCanvas>
<!--</ScrollViewer>-->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The controls that are being displayed on DynamicCanvas are of my custom type (below only the most important part):
<ContentControl x:Class="SubiektCommerceSynchro.ViewModel.TableControl"
c:DynamicCanvas.Left="{Binding X,Mode=TwoWay}"
c:DynamicCanvas.Top="{Binding Y,Mode=TwoWay}"
Width="450" Height="300"
></ContentControl>
Now the problem and the question:
The part here that doesn't work is with attached properties c:DynamicCanvas.Left(Top).
Lets put it in steps:
1) DynamicCanvas expects its immediate children to have c:DynamicCanvas.Left and c:DynamicCanvas.Top defined
2) ItemsPanel when putting TableControls onto the DynamicCanvas wraps them in some kind of container
3) DynamicCanvas sees no attached properties on its immediate children => treats them as being positioned at (0,0) and renders them effectively unmoveable.
How can I resolve this issue?

Does this help?
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="c:DynamicCanvas.Left"
Value="{Binding X,Mode=TwoWay}"/>
<Setter Property="c:DynamicCanvas.Top"
Value="{Binding Y,Mode=TwoWay}"/>
</Style>
</ItemsControl.ItemContainerStyle>

You have to modify the ControlTemplate of the item wrapper in the ItemContainerStyle. If you set it to simple ContentPresenter, the items will not be wrapped in anything (the contents of the DataTemplate will be pasted directly into the DynamicCanvas).
See this article.

Related

ItemsControl Not Creating ContentPresenter

Why isn't my ItemsControl creating a ContentPresenter for each item? I'm guessing this is what's making my items not show up (they're set to visible and in the right spot when I inspect using the Live Visual Tree). I'm basically reusing code that works up above in a different ItemsControl and I haven't been able to find anything while searching Google/Stackoverflow with this issue. I can include view model code but I don't think it's related because I see the appropriate values in the Live Property Explorer and can see each WellContainer is in it's appropriate grid cell.
XAML:
<ItemsControl
Grid.Row="1"
Grid.Column="1"
ItemsSource="{Binding Wells}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid
x:Name="m_WellGrid"
Margin="5"
wpf:GridHelpers.RowCount="{Binding RowCount}"
wpf:GridHelpers.ColumnCount="{Binding ColumnCount}">
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter
Property="Grid.Row"
Value="{Binding Path=WellRow}"/>
<Setter
Property="Grid.Column"
Value="{Binding Path=WellCol}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="A"
Margin="4"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Live Visual Tree Inspection:
The ItemsControl is designed to wrap the items in a container only when necessary, that is, when the item is not eligible to be its own container. From your comment we find that WellContainer derives from Control, thus is eligible to be its own container1 and is not wrapped in a ContentPresenter. Unfortunately there's no way to control this behavior directly, but you could subclass ItemsControl and override the ItemsControl.IsItemItsOwnContainerOverride method to modify the default behavior.
1 As we can see in the ItemsControl source code it is enough for the item to be of UIElement type to be eligible to be its own container.

How to uniquely position usercontrols from an ItemsControl (in MVVM)?

I am trying to present the usercontrol, CharacterEditorView, from within an ItemsControl. (The use of "Canvas" in the ItemsPanelTemplate can be changed, Grid?). The CharacterEditorView is to overlay on top of a character string, so position is crucial. The ItemsControl is:
<ItemsControl Grid.Row="0" Grid.RowSpan="1" ItemsSource="{Binding CharacterPads}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Margin" Value="{Binding Margin}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
where the binding of CharacterPads is defined as:
private ObservableCollection<CharacterEditorViewModel> characterPads = new ObservableCollection<CharacterEditorViewModel>();
public ObservableCollection<CharacterEditorViewModel> CharacterPads
{
get
{
return characterPads;
}
}
I would like to present EACH CharacterEditorView within a specific rectangle known only at the time of its viewmodel creation. That is, each CharacterEditorView will have a different
rectangle to be presented in:
<UserControl x:Class="Nova5.UI.Views.CharacterEditorView"
.................................................................
<Grid Background="Red">
<TextBlock Text="TextBlock" Margin="{Binding ...?????}" />
</Grid>
</UserControl>
What binding is needed to position EACH CharacterEditorView at its own position?
Thanks for any ideas.
If you want to bind the TextBlock's Margin, you need some bindable property of type Thickness within the CharacterEditorViewModel and specify this as the binding target (or, for cleaner code, provide numeric properties in the ViewModel and implement a ValueConverter that creates a Thickness instance out of them).
Alternatively, since the ItemsPanelTemplate is already defined as Canvas, bind those numeric properties directly to Canvas.Left and Canvas.Top, as HighCore suggested.

DevExpress NavBarItem won't data bind properly in ContentTemplate that uses a WPF TreeView

I'm trying to organize my NavBarItem elements in each NavBarGroup into a tree view, using the following code:
<dxn:NavBarControl DataContext="{Binding}" ItemsSource="{Binding Bars}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<dxn:NavBarControl.Resources>
<Style TargetType="dxn:NavBarGroup">
<Setter Property="Header" Value="{Binding DisplayText}"/>
<Setter Property="Content" Value="{Binding MenuItems}"/>
<Setter Property="DisplaySource" Value="Content"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TreeView x:Name="MenuView" ItemsSource="{Binding}" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=ChildItems}" >
<ContentControl>
<dxn:NavBarItem
DataContext="{Binding}"
Content="{Binding ItemText}" PreviewMouseUp="MenuItemOnPreviewMouseUp" Initialized="FrameworkContentElement_OnInitialized" />
</ContentControl>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</dxn:NavBarControl.Resources>
<dxn:NavBarControl.View>
<dxn:ExplorerBarView/>
</dxn:NavBarControl.View>
</dxn:NavBarControl>
The groups are binding correctly, and I am only populating one group, "Personnel" for debugging reasons, with the mini-hierarchy:
"Personnel"
--"Employees"
----"Contractors"
------"Time-sheets"
I get the correct structure in the treeview, but each item only has the text that would result from calling NavBarItem.ToString(). When I handle the NavBarItem.Initialized even, the sender argument, being a NavBarItem actually has the correct value in its Content property, so the binding isn't all broken, but I don't know what is, and am seeking help here.
First of all it is necessary to notice that NavBarGroup and NavBarItem are non-visual elements. Thus if you use them as the ContentPresenter content only simple strings will be shown (just like you see in your case).
The second thing is that multi-level hierarchy is not supported in NavBar (only group level and item level).
So if you need to show more than two levels you can use TreeView as you used in your sample but put TreeViewItems to the content template instead of putting NavBarItems; If you need only two levels you can use NavBarControl.ItemsSource and NavBarGroup.ItemsSource properties to populate layout.

Custom control acting as Page allowing children in XAML

I actually have two questions:
How do I modify a custom control that inherits from a FrameworkElement that only holds one child (e.g. Page or ContentControl) so that it holds multiple children (like a Panel)? Can this be done from the class definition?
How do I bind the children of a custom control to a Panel object (e.g WrapPanel) defined in the template of the custom control?
I'm not even sure if it is possible, but I want to create a custom control that behaves like a Page, or may even inherit from Page, but allows me to enter in children in XAML. For example, I would like the XAML for generating my custom control to look like this:
<CustomPage CustomAttribute="blah">
<TextBox/>
<TextBlock Text="hehe"/>
<Label Content="ha!"/>
</CustomPage>
I want to define in the style that a WrapPanel displays the children. Something like this:
<Style TargetType="{x:Type CustomPage}">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CustomPage}">
<WrapPanel/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
I would replace the WrapPanel with a ContentPresenter except that I want the ContentPresenter to behave like a WrapPanel.
I hope this is specific enough.
You can actually do exactly what you want already with an ItemsControl.
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
<TextBox/>
<TextBlock Text="hehe"/>
<Label Content="ha!"/>
</ItemsControl.Items>
</ItemsControl>
Hope that helps
EDIT: You can use the above ItemsControl as the root element in a page to gain the page's functionality.
Thank you, Murkaeus. You got me going in the right direction.
In the end, I created a custom control that inherited from Page, defining a ContentProperty for it. This apparently overrides the ContentProperty of the base class. And this works for any control type, too. Here is my code:
[ContentProperty("Children")]
class CustomPage : System.Windows.Controls.Page
{
ObservableCollection<UIElement> children = new ObservableCollection<UIElement>();
public ObservableCollection<UIElement> Children { get { return children; } set { children = value; } }
}
Then I defined the template for my control in the Themes/Generic.xaml file with an ItemsControl using my custom ContentProperty as a source:
<Style TargetType="local:CustomPage">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ItemsControl ItemsSource="{Binding Children,
RelativeSource={RelativeSource TemplatedParent}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I hope this helps anyone else who is looking for a solution to the same problem. And thank you again, Murkaeus, for your help!
EDIT: I should warn everyone that if you use this method, all your data bindings are lost for some reason, I discovered this with further experimentation. I ended up giving up and just specifying a panel as the child of my custom page object.

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