How can I get controls inside the custom control? - wpf

I made a Custom Control, here is the XAML:
<Style TargetType="{x:Type local:Sheet}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Sheet}">
<Grid Grid.IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="71"></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="18"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Border Background="{TemplateBinding Background}" Grid.Column="1" Grid.Row="1"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" VerticalAlignment="{TemplateBinding VerticalAlignment}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}">
<Grid x:Name="G">
<Border Background="#59217346"></Border>
</Grid>
</Border>
<Button Background="#e6e6e6" BorderBrush="#999999" BorderThickness="0,0,1,1" Visibility="Collapsed">◢</Button>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I need to access the Grid which x:Name="G" to add&remove its children.
I found a topic about this:Access children of Custom Control wpf c#
It suggests using a Dependency Property. However, I can't bind the Children of the Grid yet.
There is another solution by using a VisualTreeHelper to get the Grid directly.
Meanwhile, the VisualTreeHelper can only get the child while the control is completely loaded, or it only returns 0 children.
I need to initialize its children while creates it and hardly wait the control loaded. For example, whenever I set a Dependency Property Rows of the Custom Control to 4, the Grid which x:Name="G" will add&remove its RowDefinition to 4.
How can I solve this? Please help me.

Inside the CustomControl override OnApplyTemplate and use FrameworkElement.GetTemplateChild(String) to get a named element that is part of the template. OnApplyTemplate is invoked when the controls visual tree is created (template applied) and measured, but before the Loaded event.
public override void OnApplyTemplate()
{
var grid = GetTemplateChild("G") as Grid;
grid.RowDefinitions.Add(new RowDefinition());
}

use var g = (Grid)Control.Template.FindName("G",Control), here Control is the name of the the control you set the style to. it can be this. Cast what it returns to the the type of the the "G". Here (Grid). Then access all the children by saying g.Children[index] you can access the its properties like g.Row g.RowDefinations;

Related

Not all content is clickable on custom control derived from button

iv'e got a "Cube" (Dice) control which derives from Button
Cube :
public class Cube : Button
{
public Cube()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Cube), new FrameworkPropertyMetadata(typeof(Cube)));
}
...... // Stuff
}
Template (In general):
<ControlTemplate TargetType="{x:Type local:Cube}" x:Key="CubeControlTemplate">
<Border>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border>
<Grid>
.......
</Grid>
</Border>
<Border Grid.Column="2">
<Grid>
.......
</Grid>
</Border>
<Grid>
</Border>
</ControlTemplate>
what it looks like :
the yellow marking shows that it is clickable only behind the Content , only if you really target your click where the Button is "Hidden" ..
any ideas why this happens ?
Without a Background mouse events will not be catched. Give your outer Border a transparent Color:
<ControlTemplate TargetType="{x:Type local:Cube}" x:Key="CubeControlTemplate">
<Border Background="Transparent">
.......
</Border>
</ControlTemplate>
Please check that you are not trying to click on an element that has a Null background. Hit testing requires a Brush to be set.
If your Border or your Grid has a Null background try setting it to Transparent.
If you are still having issues try debugging with Snoop.
http://snoopwpf.codeplex.com/

Having Trouble Styling and Editing AvalonDock Document Tab Panel

I'm trying to figure out how to make the VS2010 style for AvalonDock to function a little more like VS2010. The problem that I'm running into is that when there are more tabs than can fit in the header area there is no indication to the user that there are more tabs.
I thought that the tab headers were just clipped and not visible. I have a custom copy of the VS2010 style and went to the DocumentPane style:
<!--DocumentPane-->
<Style x:Key="{x:Type ad:DocumentPane}" TargetType="{x:Type ad:DocumentPane}"> ...
And found the tab headers (I think) as a "ad:DocumentTabPanel". I wrapped this in a ScrollViewer:
<ScrollViewer Style="{StaticResource ResourceKey=TabHeaderScrollViewer}" CanContentScroll="True">
<ad:DocumentTabPanel
x:Name="paneTabsPanel"
Panel.ZIndex ="1"
IsItemsHost="True"
TabItemStyle="{StaticResource CustomDocumentTabItemStyle}"/>
</ScrollViewer>
The scroll viewer is setup to have a custom style on it:
<Style x:Key="TabHeaderScrollViewer" TargetType="ScrollViewer">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ScrollViewer">
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<RepeatButton Command="ScrollBar.PageLeftCommand"></RepeatButton>
<ScrollContentPresenter Grid.Column="1"
x:Name="ScrollContentPresenter"
Cursor="{TemplateBinding Cursor}"
Margin="{TemplateBinding Padding}"
ContentTemplate="{TemplateBinding ContentTemplate}"/>
<RepeatButton Grid.Column="2" Command="ScrollBar.PageRightCommand"></RepeatButton>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The problem I am having is that even when I load up a ton of tabs the scrolling doesn't work. I don't think this is an issue with my styling. It appears that the tab headers aren't in the VisualTree or have a collapsed visibility. I've been going through the AvalonDock source for a while, but I can't see how the headers are hidden.
I've already had to subclass some of the AvalonDock classes because I needed additional properties on them.
Can someone either explain/help me come up with a solution to scroll the tabs?
Problem was solved very simply. I had to set the HorizontalScrollBarVisibility="Auto". I checked http://msdn.microsoft.com/en-us/library/system.windows.controls.scrollviewer.horizontalscrollbarvisibility.aspx to see what the default value is for this property, but didn't see one there or on the ScrollViewer page. I assume it defaults to "Hidden".
Anyhow, once set to "Auto" the repeat buttons are shown only when the tabs overflow.

Customize WPF databinding: How to add custom logic?

i have a question regarding some complex data-binding.
I want to be able to update a grid (which has the property "IsItemsHost" set to true)
dynamically whenever a data-binding occurs.
Actually i am using a CustomControl which is an ItemsControl and this
has the Grid in its ControlTemplate.
To be more specific, i bind the grid to some items and i want to
change the number of grid rows depending on these items,
add something like a header (one row containing some text),
and set the items' Grid.Row and Grid.Column using some custom logic.
What is the easiest way to apply such behaviour
whenever the bound data is updated?
Do i have to use a viewmodel that also contains the header data?
Thanks in advance.
Code of the CustomControl Generic.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TimeTableControl">
<Style TargetType="{x:Type local:TimeTableControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TimeTableControl}">
<Border Width="Auto" Height="Auto" BorderBrush="#FF4B5A9B" BorderThickness="4" CornerRadius="4" Margin="2" Padding="0" Background="White">
<Grid Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="0.1*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Viewbox>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DayCaption}"/>
</Viewbox>
<Border Grid.Row="1" BorderThickness="0,2,0,0" BorderBrush="#FF4B5A9B">
<Grid Name="ContentGrid" IsItemsHost="True">
</Grid>
</Border>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Grid is used for layout. If you have a changing number of items in some collection, what you really want is ItemsControl, or more specific ListBox (if you want item selection, etc).
If you still want Grid-like behavior of individual rows, you may want to define a Grid in ItemsControl.ItemTemplate and play with Grid.IsSharedSizeScope at ItemsControl level. Alternatively, you may just use ListView instead to get the grid look and item selection in a package.
Update: I got it working by creating a custom panel that uses MeasureOverride and ArrangeOverride to update itself. This lets me adjust the panel to the children and I don't even need to use a grid. This also makes the control lookless.

WPF: Grid with column/row margin/padding?

Is it easily possible to specify a margin and/or padding for rows or columns in a WPF Grid?
I could of course add extra columns to space things out, but this seems like a job for padding/margins (it will give much simplier XAML). Has someone derived from the standard Grid to add this functionality?
RowDefinition and ColumnDefinition are of type ContentElement, and Margin is strictly a FrameworkElement property. So to your question, "is it easily possible" the answer is a most definite no. And no, I have not seen any layout panels that demonstrate this kind of functionality.
You can add extra rows or columns as you suggested. But you can also set margins on a Grid element itself, or anything that would go inside a Grid, so that's your best workaround for now.
Use a Border control outside the cell control and define the padding for that:
<Grid>
<Grid.Resources >
<Style TargetType="Border" >
<Setter Property="Padding" Value="5,5,5,5" />
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Grid.Column="0">
<YourGridControls/>
</Border>
<Border Grid.Row="1" Grid.Column="0">
<YourGridControls/>
</Border>
</Grid>
Source:
Original Source
and from Way Back Machine
You could use something like this:
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Padding" Value="4" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Border Padding="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
Or if you don't need the TemplateBindings:
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Border Padding="4">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Thought I'd add my own solution because nobody yet mentioned this. Instead of designing a UserControl based on Grid, you can target controls contained in grid with a style declaration. Takes care of adding padding/margin to all elements without having to define for each, which is cumbersome and labor-intensive.For instance, if your Grid contains nothing but TextBlocks, you can do this:
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="10"/>
</Style>
Which is like the equivalent of "cell padding".
I am surprised I did not see this solution posted yet.
Coming from the web, frameworks like bootstrap will use a negative margin to pull back rows / columns.
It might be a little verbose (albeit not that bad), it does work and the elements are evenly spaced and sized.
In the example below I use a StackPanel root to demonstrate how the 3 buttons are evenly spaced using margins. You could use other elements, just change the inner x:Type from button to your element.
The idea is simple, use a grid on the outside to pull the margins of elements out of their bounds by half the amount of the inner grid (using negative margins), use the inner grid to evenly space the elements with the amount you want.
Update:
Some comment from a user said it doesn't work, here's a quick video demonstrating: https://youtu.be/rPx2OdtSOYI
<StackPanel>
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Grid}">
<Setter Property="Margin" Value="-5 0"/>
</Style>
</Grid.Resources>
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Margin" Value="10 0"/>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Content="Btn 1" />
<Button Grid.Column="1" Content="Btn 2" />
<Button Grid.Column="2" Content="Btn 3" />
</Grid>
</Grid>
<TextBlock FontWeight="Bold" Margin="0 10">
Test
</TextBlock>
</StackPanel>
Edited:
To give margin to any control you could wrap the control with border like this
<!--...-->
<Border Padding="10">
<AnyControl>
<!--...-->
You could write your own GridWithMargin class, inherited from Grid, and override the ArrangeOverride method to apply the margins
I did it right now with one of my grids.
First apply the same margin to every element inside the grid. You can do this mannualy, using styles, or whatever you like. Lets say you want an horizontal spacing of 6px and a vertical spacing of 2px. Then you add margins of "3px 1px" to every child of the grid.
Then remove the margins created around the grid (if you want to align the borders of the controls inside the grid to the same position of the grid). Do this setting a margin of "-3px -1px" to the grid. That way, other controls outside the grid will be aligned with the outtermost controls inside the grid.
I ran into this problem while developing some software recently and it occured to me to ask WHY? Why have they done this...the answer was right there in front of me. A row of data is an object, so if we maintain object orientation, then the design for a particular row should be seperated (suppose you need to re-use the row display later on in the future). So I started using databound stack panels and custom controls for most data displays. Lists have made the occasional appearance but mostly the grid has been used only for primary page organization (Header, Menu Area, Content Area, Other Areas). Your custom objects can easily manage any spacing requirements for each row within the stack panel or grid (a single grid cell can contain the entire row object. This also has the added benefit of reacting properly to changes in orientation, expand/collapses, etc.
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<custom:MyRowObject Style="YourStyleHereOrGeneralSetter" Grid.Row="0" />
<custom:MyRowObject Style="YourStyleHere" Grid.Row="1" />
</Grid>
or
<StackPanel>
<custom:MyRowObject Style="YourStyleHere" Grid.Row="0" />
<custom:MyRowObject Style="YourStyleHere" Grid.Row="1" />
</StackPanel>
Your Custom controls will also inherit the DataContext if your using data binding...my personal favorite benefit of this approach.
I had similar problem recently in two column grid, I needed a margin on elements in right column only. All elements in both columns were of type TextBlock.
<Grid.Resources>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource OurLabelStyle}">
<Style.Triggers>
<Trigger Property="Grid.Column" Value="1">
<Setter Property="Margin" Value="20,0" />
</Trigger>
</Style.Triggers>
</Style>
</Grid.Resources>
One possibility would be to add fixed width rows and columns to act as the padding / margin you are looking for.
You might also consider that you are constrained by the size of your container, and that a grid will become as large as the containing element or its specified width and height. You could simply use columns and rows with no width or height set. That way they default to evenly breaking up the total space within the grid. Then it would just be a mater of centering your elements vertically and horizontally within you grid.
Another method might be to wrap all grid elements in a fixed with single row & column grid that has a fixed size and margin. That your grid contains fixed width / height boxes which contain your actual elements.
in uwp (Windows10FallCreatorsUpdate version and above)
<Grid RowSpacing="3" ColumnSpacing="3">
Though you can't add margin or padding to a Grid, you could use something like a Frame (or similar container), that you can apply it to.
That way (if you show or hide the control on a button click say), you won't need to add margin on every control that may interact with it.
Think of it as isolating the groups of controls into units, then applying style to those units.
As was stated before create a GridWithMargins class.
Here is my working code example
public class GridWithMargins : Grid
{
public Thickness RowMargin { get; set; } = new Thickness(10, 10, 10, 10);
protected override Size ArrangeOverride(Size arrangeSize)
{
var basesize = base.ArrangeOverride(arrangeSize);
foreach (UIElement child in InternalChildren)
{
var pos = GetPosition(child);
pos.X += RowMargin.Left;
pos.Y += RowMargin.Top;
var actual = child.RenderSize;
actual.Width -= (RowMargin.Left + RowMargin.Right);
actual.Height -= (RowMargin.Top + RowMargin.Bottom);
var rec = new Rect(pos, actual);
child.Arrange(rec);
}
return arrangeSize;
}
private Point GetPosition(Visual element)
{
var posTransForm = element.TransformToAncestor(this);
var areaTransForm = posTransForm.Transform(new Point(0, 0));
return areaTransForm;
}
}
Usage:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:GridWithMargins ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Rectangle Fill="Red" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<Rectangle Fill="Green" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<Rectangle Fill="Blue" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</local:GridWithMargins>
</Grid>
</Window>
Sometimes the simple method is the best. Just pad your strings with spaces. If it is only a few textboxes etc this is by far the simplest method.
You can also simply insert blank columns/rows with a fixed size. Extremely simple and you can easily change it.

Can I have two ContentPresenter pointing to in single Content or ContentSource in the ContentTemplate?

I have created a NavigationPane just like Outlook 2007. In Outlook, when the Pane is collapsed and the side bar is clicked, it used to popup the Selected NavigationItem content. I mimicked the same behavior using contentpresenter in the ControlTemplete (one for the TabControl's SelectItemHost and another for the Popup). But the problem is when the Popup is open up, the NavigationPane selected content when away and it appears when we switch back to the same navigation item from another navigation item. I am using TabControl and TabItem as NavigationPane and NavigationPaneItem.
I am pointing the "SelectedContent" as the ContentSource for the two ContentPresenter
You can define two ContentPresenter objects within a control template and point them both at the same content source if you like:
<ControlTemplate x:Key="WeirdButton" TargetType="Button">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Grid.RowSpan="2" Background="{TemplateBinding Background}" />
<ContentPresenter ContentSource="Content"/>
<ContentPresenter ContentSource="Content" Grid.Row="1"/>
</Grid>
</ControlTemplate>
This has some rather unusual side effects, however. Because you can't put the same visual into two places in the visual tree, this template will only work as expected if the child content of the button is NOT a visual (or derived from Visual). If the content is some other type of data and the visuals are generated by a data template everything works as expected. Setting the content of the button to a string (<Button Content="OK"/>) works also.
Note that this same effect could be achieved with a visual brush:
<ControlTemplate x:Key="WeirdButton" TargetType="Button">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Grid.RowSpan="2" Background="{TemplateBinding Background}" />
<ContentPresenter x:Name="presenter" ContentSource="Content"/>
<Rectangle Grid.Row="1"
Width="{Binding ActualWidth, ElementName=presenter}" Height="{Binding ActualHeight, ElementName=presenter}">
<Rectangle.Fill>
<VisualBrush Visual="{Binding ElementName=presenter}" Stretch="None" AlignmentX="Left"/>
</Rectangle.Fill>
</Rectangle>
</Grid>
</ControlTemplate>
The drawback of this approach is that you can't interact with controls in the visual brush. So if you want the buttons, textboxes, and other controls on the duplicate to also be interactive, you will have to follow an approach closer to the first template.

Resources