How to get GridSplitter to move between extremes - wpf

I have a Gridsplitter in a vertical grid and ideally what would like to see two buttons in the GridSplitter. An up button would automatically move the splitter to the highest top position and a bottom button would move it all the way down. However, the GridSplitter cannot contain other items. Any thoughts on a way around this? I thought of just making a panel and then sandwiching it between two GridSplitters?

GridSplitter inherits from Control, so all you need to do is define a template for it that includes the two buttons:
<ControlTemplate x:Key="SplitterWithButtons" TargetType="{x:Type GridSplitter}">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}">
<DockPanel>
<Button DockPanel.Dock="Left" Content="{StaticResource UpArrow}" Click="OnSplitterUpButton" />
<Button DockPanel.Dock="Right" Content="{StaticResource DownArrow}" Click="OnSplitterDownButton" />
</DockPanel>
</Border>
</ControlTemplate>
...
<GridSplitter Template="{StaticResource SplitterWithButtons}" ... />
Inside your event handlers you can find the GridSplitter like this:
private void OnSplitterUpButton(object sender, RoutedEventArgs e)
{
var splitter = ((Button)sender).TemplatedParent as GridSplitter;
...
}

Related

ContentPresenter loosing it data binding after triggers invoked more than twice

I have created an extended button with 2 different border styles invoked by triggers in XAML. Both share the same contentpesenter but after changing the border style more than twice the content in the contentpresenter fails to display.
Below is a link to the entire project with a test bed application that demonstrates the issue, I think the issue is somewhere in the XAML below but I cannot see why it breaks:
Sample Button App
<Style.Resources>
<ContentPresenter x:Key="ButtonContent" Margin="5" HorizontalAlignment="Center"
Content="{Binding Content}"/>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid Margin="{Binding KeyMargin}">
<Grid Visibility="{Binding RectangleVisibility}">
<Grid.OpacityMask>
<VisualBrush Visual="{Binding ElementName=rectBorder}" />
</Grid.OpacityMask>
<Border x:Name="rectBorder"
CornerRadius="{Binding BorderCorners}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
BorderThickness="{Binding BorderThickness}"/>
<Viewbox Stretch="Fill"
StretchDirection="Both">
<ContentControl Content="{StaticResource ButtonContent}"/>
</Viewbox>
</Grid>
<Grid Visibility="{Binding EllipseVisibility}">
<Ellipse Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="{Binding BorderThickness}"
Fill="{TemplateBinding Background}">
</Ellipse>
<Viewbox Stretch="Fill"
StretchDirection="Both">
<ContentControl Content="{StaticResource ButtonContent}"/>
</Viewbox>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
The problem is most likely that you cannot have the same element (the ContentPresenter in this case) in more than one place in the visual tree, and in which one of the two grids it ends up is undefined, i.e., an implementation archetype of WPF.
To get the element duplicated this might work:
<ContentControl Content="{TemplateBinding Content}"/>
or in your case
<ContentControl Content="{TemplateBinding Content}" Margin="5" HorizontalAlignment="Center"/>
instead of a static resource. The <ContentPresenter/> syntax is pretty much an optimized shortcut for that (or you could set x:Shared="False" on the resource, but having a ContentPresenter as a static resource is as far as I know not how it is intended to be used)
If the Button content is a UIElement itself though, it will be used directly itself in the visual tree, i.e., twice and this wont work either. A better solution would be to just have the content once in the control template and change the visual appearance around it, e.g., using a trigger to set the Grid's OpacityMask.
Another remark is that your control template is very tightly bound to where the Button is used, with direct bindings to the current data context, which reduces its reusability. Some easy fixes is to use TemplateBinding instead of Binding for BorderThickness respectively Margin (instead of KeyMargin), since those are existing properties of the Button.
For better reusability and cleaner code you should consider looking into creating a custom control deriving from Button with dependency properties for BorderCorners, the desired visual state (ellipse vs rectangle) etc. You might also want to use triggers to get the mouse-over effects of the button etc. Have fun control templating!

Get the scroll position from a RichTextBox?

I've created a highlighting mechanism for a RichTextBox in Silverlight 4. It'll get character positions and draw rectangle(s) over the text.
The trouble I have now is with scrolling on the RichTextBox. As I scroll all of my precious highlighting gets left behind. Is there any way I can add an event handler to a scroll event and/or a scrolling position of the RichTextBox? Or is there some better way in which I can link the position of the highlighting rectangles to the RichTextBox?
The trick would be to get what ever panel (I guess its a Canvas?) that you are overlaying the RichTextBox with to actually exist within the same ScrollViewer that rich text exists in.
The following is very rough idea but should get you on the path to reasonable solution.
You can do this using a custom style for the RichTextBox. The default style for this control can be found here.
Copy this style into a resource in your containing UserControl and point your RichTextBox Style property at it. So far nothing is different but now you can play about with the template. The relevant portion currently looks like this:-
<Border x:Name="MouseOverBorder" BorderThickness="1" BorderBrush="Transparent">
<ScrollViewer x:Name="ContentElement" Padding="{TemplateBinding Padding}" BorderThickness="0" IsTabStop="False" />
</Border>
Now we can tweak it like this:-
<Border x:Name="MouseOverBorder" BorderThickness="1" BorderBrush="Transparent">
<ScrollViewer Padding="{TemplateBinding Padding}" BorderThickness="0" IsTabStop="False">
<Grid>
<ContentControl x:Name="ContentElement" />
<Canvas x:Name="HighlightOverlay" />
</Grid>
</ScrollViewer>
</Border>
You'll note that we've moved the name "ContentElement" from the ScrollViewer to the new ContentControl. Having a FrameworkElement called "ContentElement" is the only feature that the RichTextBox stipulates about its template.
Now overlaying this ContentControl we can place a Canvas where you can place your highlighting rectangles. If the user scrolls this RichTextBox the whole Grid containing both the Content and the Highlights will scroll together.
The only remaining trick is acquiring the "HighlightOverlay" so that you can add your rectangle to it. Here is some code that will grab it:-
private Canvas HightlightOverlay;
public MyUserControl()
{
InitializeComponent();
MyRichText.LayoutUpdated += MyRichText_LayoutUpdated;
}
void MyRichText_LayoutUpdated(object sender, EventArgs e)
{
HightlightOverlay = MyRichText.Descendents()
.OfType<Canvas>()
.FirstOrDefault(elem => elem.Name == "HighlightOverlay");
}
You will be wondering where the Descendents method is coming from, it is here.
Anthony W Jones came up with a brilliant solution. There were just a couple tweaks to the XAML I had to make.
As suggested I started with this inside the template:
<Border x:Name="MouseOverBorder" BorderThickness="1" BorderBrush="Transparent">
<ScrollViewer Padding="{TemplateBinding Padding}" BorderThickness="0" IsTabStop="False">
<Grid>
<ContentControl x:Name="ContentElement" />
<Canvas x:Name="HighlightOverlay" />
</Grid>
</ScrollViewer>
</Border>
But the ContentControl messed things up somehow and you can't actually type into the RichTextBox anymore. Also, the scroll bars weren't showing up.
But I found the two changes necessary to make this work:
<Border x:Name="MouseOverBorder" BorderBrush="Transparent" BorderThickness="1">
<ScrollViewer BorderThickness="0" IsTabStop="False" Padding="{TemplateBinding Padding}" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid>
<Grid x:Name="ContentElement" />
<Canvas x:Name="HighlightOverlay" />
</Grid>
</ScrollViewer>
</Border>
Adding HorizontalScrollBarVisibility="Auto" and VerticalScrollBarVisibility="Auto" brought the scroll bars back, and simply using Grid instead of the ContentControl made the RichTextBox editable again.

WPF tab control spacing between headers

The default behavior of the WPF Tabcontrol is to place the Tab Headers adjacent to each other, without any empty space in between. What if I wanted to specify a gap between the headers? Do I have to define a control template for this? I'm relatively new to WFP and any help is appreciated.
Thanks
I believe you will need to define a custom control template for the TabItem, maybe even one for the TabControl. Here is an example of a TabItem that uses a spacer for some separation.
<Style
x:Key="SpacedTab"
TargetType="{x:Type TabItem}">
<Setter
Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="{x:Type TabItem}">
<Border
x:Name="Spacer"
Width="Auto"
Height="Auto"
Padding="0 0 5 0"
Margin="0 0 0 0"
BorderBrush="Transparent"
BorderThickness="0">
<Border
x:Name="Border"
MinWidth="150"
Width="Auto"
Height="30"
Background="Gray"
BorderBrush="DarkGray"
BorderThickness="0,0,0,0"
CornerRadius="6,6,0,0"
Cursor="Hand"
VerticalAlignment="Bottom">
<ContentPresenter
x:Name="ContentSite"
TextElement.FontSize="10pt"
TextElement.FontFamily="Arial"
TextElement.Foreground="Black"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="8,3,8,3"
Width="Auto"
Height="Auto" />
</Border>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Hopefully that is a nudge in the right direction; you will still need to add that as a style resource and reference it from your TabControl -> TabItem.
It is easy to add space by doing it in the designer. Select the Tab you want to move, by starting with the rightmost tab. Then hold ctrl and use the right arrow key to move the tab to the right. Do the same with the rest of the tabs. Then you can manually adjust the margin in the xaml code.

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.

Silverlight Scrollviewer With Only Buttons

I am using a ScrollViewer as part of my Silverlight application. It has a horizontal orientation, and I would like it to appear such that only the scroll buttons appear, but not the scroll bar itself. Something like this crude ASCII rendering:
------------------------------------------------------
| | | |
| < | Content Here | > |
| | | |
------------------------------------------------------
I know I could use the templating functionality, but all the samples I've seen only change the look of all the elements, and not their raw positioning, or whether they even appear. Is it possible to do this, and could someone provide an outline of what the template might look like?
Here is another option. Override the default template for SCrollviewer and handle the buttons as PageUp/PageDown. My example below is a scrollviewer that scrolls vertically. You can easily change to to horizontal scrolling and change the handlers from PageUp/PageDown to Left and Right handlers.
<ControlTemplate TargetType="{x:Type ScrollViewer}" x:Key="ButtonOnlyScrollViewer">
<ControlTemplate.Resources>
<!-- Add style here for repeat button seen below -->
</ControlTemplate.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<RepeatButton Grid.Row="0"
Foreground="White"
Background="Yellow"
HorizontalAlignment="Stretch"
Command="ScrollBar.PageUpCommand"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}">
</RepeatButton>
<ScrollContentPresenter
CanContentScroll="{TemplateBinding CanContentScroll}"
Grid.Row="1"
Content="{TemplateBinding Content}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Margin="{TemplateBinding Margin}"/>
<RepeatButton Grid.Row="2" Background="Black" Foreground="White" Command="ScrollBar.PageDownCommand">
</RepeatButton>
</Grid>
</ControlTemplate>
I've done something similar and the best way I found to do this was to put your content in a scroll viewer and just turn off the scrollbars. Then code your buttons to scroll the scrollviewer.
Edit: Responding to comment about no way to deal with sizing.
First off, you would build this control as a ContentControl. It should have a template defined in generic.xaml that has your button controls plus the scroll viewer. Something like:
<Canvas x:Name="root">
<Button x:Name="left" Content="<"/>
<Button x:Name="right" Content=">"/>
<ScrollViewer x:Name="viewer" BorderThickness="0" VerticalScrollBarVisibility="Hidden">
<ContentPresenter />
</ScrollViewer>
</Canvas>
Then in your control you would need to override OnApplyTemplate:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
left = GetTemplateChild("left") as Button;
left.Click += new RoutedEvent(YourHandler);
right = GetTemplateChild("right") as Button;
right.Click += new RoutedEvent(YourHandler);
// position your scroll buttons here, not writing that code
scroll = GetTemplateChild("viewer") as ScrollViewer;
root = GetTemplateChild("root") as Canvas;
var fe = this.Content as FrameworkElement;
if (fe != null)
{
fe.SizeChanged += new SizeChangedEventHandler(fe_SizeChanged);
}
}
void fe_SizeChanged(object sender, SizeChangedEventArgs e)
{
this.InvalidateMeasure();
}
protected override Size ArrangeOverride(Size finalSize)
{
if (!double.IsInfinity(scroll.ViewportHeight))
{
left.Visibility = (scroll.HorizontalOffset > 0);
right.Visibility = (scroll.HorizontalOffset < ScrollableHeight);
}
return base.ArrangeOverride(finalSize);
}
protected override Size MeasureOverride(Size availableSize)
{
scroll.Measure(availableSize);
return scroll.DesiredSize;
}
In your button click handlers you would need to (1) scroll the viewer and (2) check the new value of the HorizontalOffset to see if you need to hide or show either of the button.
Disclaimer: This code probably doesn't work as is since it was written by hand and based on a different example.
i found the solution here :)
http://weblogs.asp.net/fredriknormen/archive/2009/09/18/create-an-automatic-scrollable-image-slider-in-silverlight.aspx
This is made using a DispatcherTimer, really nice example :)
I have been searching for working solution for quite a lot of time now. And based on Louis's solution I have managed to make it working. (in WPF)
This solution is for horizontal scrolling.
Firstly, add ListView:
<ListView ItemsSource="{Binding Items}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.Template>
<ControlTemplate>
<ScrollViewer Template="{StaticResource ButtonOnlyScrollViewer}">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ListView.Template>
</ListView>
And a modified template from Louis's answer for horizontal scrolling:
<ControlTemplate TargetType="{x:Type ScrollViewer}" x:Key="ButtonOnlyScrollViewer">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<RepeatButton Content="<"
Grid.Column="0"
Command="ScrollBar.LineLeftCommand"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
<ScrollContentPresenter
CanContentScroll="{TemplateBinding CanContentScroll}"
Grid.Column="1"
Content="{TemplateBinding Content}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Margin="{TemplateBinding Margin}"/>
<RepeatButton Content=">"
Grid.Column="2"
Command="ScrollBar.LineRightCommand"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
</Grid>
</ControlTemplate>

Resources