Custom control acting as Page allowing children in XAML - wpf

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.

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.

Binding to Template's parent's parent's child's property

I have an Expander style which applied template on both Header and Content
I wish to have one of the TextBlock inside content's template to match the Header's TextBlock's Foreground color
<Style TargetType="Expander">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Foreground="Blue"/> <!--Header TextBlock-->
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock/> <!--Match Header TextBlock's Foreground-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
I have tried ElemenName binding but it seems like the name scope is different since I am 2 template level deep.
I thought about TemplateBinding but I only want one of the column in the content to match the color of header instead of the whole expander.
I could apply the same trigger for the Header TextBlock on the Content TextBlock too but I am trying to see if there is a way to avoid duplicating the code.
ElementName can't work across templates; with a template, you could have multiple elements with the same name.
Anything with different template instances reaching out to grope each other via the visual tree is going to be fraught with nameless horrors, whatever you do.
Instead, I would suggest that they both get their brushes from the same source. This is much more in line with how WPF is happy doing things.
If the color won't change, use an appropriately-named Brush resource for both.
If it will change, bind both to a viewmodel Brush property (kinda squicky, but not the end of the world), or use triggers driven by some other viewmodel property which represents the state being indicated by the color. The triggers would reference any number of appropriately-named Brush resources: ErrorBrush, HappyBrush, SadBrush, etc. By "name" I mean x:Key of course:
<SolidColorBrush x:Key="HappyBrush">GreenYellow</SolidColorBrush>
...etc.

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.

x:Shared MarkupExtension in Silverlight

Is there a workaround for the missing x:Shared MarkupExtension in silverlight?
I have the following Xaml which is creating an ellipse on each target series. I need the ellipses to be unique as they are later added to canvas. By using this Xaml I get the error that the UIElement has already been added to another parent (e.g. single Ellipse instance added to Canvas multiple times).
In WPF I simply use the x:Shared property on this style to get it to work.
<!-- Set the style for the series -->
<Style TargetType="SciChart:FastLineRenderableSeries" >
<Setter Property="SeriesColor" Value="#FF93F2C1"/>
<Setter Property="ResamplingMode" Value="Mid"/>
<Setter Property="RolloverMarker">
<Setter.Value>
<Ellipse Width="9" Height="9" Fill="#7793F2C1" Stroke="#FFA3FFC9"/>
</Setter.Value>
</Setter>
</Style>
A workaround I considered was to create a control called RolloverMarker and set its control template. I'd appreciate any direct or indirect solutions to this problem.
If you are dynamically adding objects to a panel, then a new object needs to be created each time, or you need to define your control in some kind of Template and add a new data object which will use the Template. You cannot add the same item multiple times.
For example,
// Does not work
var templateItem = new FastLineRenderableSeries();
myCanvas.Add(templateItem);
myCanvas.Add(templateItem);
// Works
myCanvas.Add(new FastLineRenderableSeries());
myCanvas.Add(new FastLineRenderableSeries());
Or
<ItemsControl ItemsSource="{Binding SomeCollection}"
ItemTemplate="{StaticResource FastLineRenderableSeriesStyle}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
// Add items. They'll get rendered with defined ItemStyle.
var templateItem = new FastLineRenderableSeries();
SomeCollection.Add(templateItem);
SomeCollection.Add(templateItem);

ItemsControl and controls attached properties

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.

Resources