Access Control Template's children inside code in WPF - c#

This thumb is used inside a custom control slider. Is there any way I can access thumbBorder inside the code?
<Style x:Key="SliderThumbStyle" TargetType="Thumb">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="OverridesDefaultStyle" Value="false" />
<Setter Property="Height" Value="18" />
<Setter Property="Width" Value="18" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Grid>
<Border x:Name="thumbBorder"
CornerRadius="9,0,0,9"
Background="DimGray" />
<Ellipse x:Name="thumbEllipse"
Fill="Orange">
</Ellipse>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

You can use the VisualTreeHelper to navigate through the visual tree of you thumb and find any child (border/ellipse) in it.
Below is the simplest way to find the child of particular type
private static DependencyObject RecursiveVisualChildFinder<T>(DependencyObject rootObject)
{
var child = VisualTreeHelper.GetChild(rootObject, 0);
if (child == null) return null;
return child.GetType() == typeof (T) ? child : RecursiveVisualChildFinder<T>(child);
}
you can update this function to check the child Name also to return

Related

Removing full screen in xaml dynamically

This will probably be very simple for most of you, I am new to XAML and WPF.
I have an app that startes att full screen, I did this by adding
WindowState="Maximized"
WindowStyle="None"
I want to have a button that simply eliminates this part. I have a "Full screen" button in the xaml and by click he is calling a "FullScreen_Click" function in my code.
I just need to know what to write in the code that will eliminate the full screen if it is on full screen mode and restore it to full screen when it is not.
Try this:
private void FullScreen_Click(object sender, RoutedEventArgs e)
{
WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
}
This will toggle between WindowState.Maximized and WindowState.Normal each time the Button is clicked.
For a xaml only technique just for reference to see a xaml example in comparison (but I would do #mm8's route, it's simpler);
1. Bind your property to that of another like:
<Window WindowState="{Binding Tag, ElementName=toggleState}" .... />
2. Use a `ToggleButton` or similar control and `Triggers`
.
<!-- like this PoC -->
<Grid>
<Grid.Resources>
<Style x:Key="cwWindowState_PoC" TargetType="{x:Type ToggleButton}">
<Setter Property="Tag" Value="Maximized"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid>
<Border Background="{TemplateBinding Background}"/>
<ContentPresenter x:Name="MyContentPresenter"
Content="{TemplateBinding Tag}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Tag" Value="Normal" />
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Tag" Value="Maximized" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<ToggleButton x:Name="toggleState" Content="Click Me"
Background="Green"
Style="{StaticResource cwWindowState_PoC}"/>
</Grid>
Could also use DataTrigger but that requires interaction triggers instead of just a property setter from a template.

WPF Custom Control Button Content Goes Missing With More Than One Button

To begin with, this is in .NET 4.0 because it has to be. I know some bugs have been fixed in later versions of .NET, so if this is an actual .NET bug, I guess I'm going to have to live with using user controls which don't seem to have this issue.
I created a custom control library in WPF to make customizable buttons that will be used in 3rd party software. I seem to have an issue, however, with multiple buttons resulting in the content for all but one of the buttons to go missing. I have confirmed the problem in SNOOP. The content just isn't there. The SNOOP tree gets as far as the content presenter and then there's nothing under it, except for the one button that does have content. I've created a very bare bones example of the problem.
My Library's Generic.xaml is as follows:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:CustomControlsLibrary.Controls">
<Style x:Key="CustomButtonStyle" TargetType="{x:Type controls:CustomButton}">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:CustomButton}">
<Border CornerRadius="{TemplateBinding CornerRadius}" BorderThickness="3" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" ContentSource="Content" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="Button1Style" TargetType="{x:Type controls:Button1}" BasedOn="{StaticResource CustomButtonStyle}" >
<Setter Property="CornerRadius" Value="4" />
<Setter Property="BorderBrush" Value="White" />
<Setter Property="Height" Value="40" />
<Setter Property="Width" Value="100" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Content">
<Setter.Value>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=controls:Button1}, Path=Text}" />
</Setter.Value>
</Setter>
</Style>
The two control classes are as follows:
CustomButton:
public class CustomButton : Button
{
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(CustomButton), new FrameworkPropertyMetadata(new CornerRadius(0)));
public CornerRadius CornerRadius
{
get { return (CornerRadius)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton), new FrameworkPropertyMetadata(typeof(CustomButton)));
}
}
Button1:
public class Button1 : CustomButton
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(Button1), new FrameworkPropertyMetadata(""));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
static Button1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Button1), new FrameworkPropertyMetadata(typeof(Button1)));
}
}
I then create a simple WPF application with just a main window with all logic in MainWindow.xaml:
<Window x:Class="CustomControlLibraryTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:CustomControlsLibrary.Controls;assembly=CustomControlsLibrary"
Title="MainWindow" Height="350" Width="525" Background="DarkGray">
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/CustomControlsLibrary;component/Themes/Generic.xaml" />
</Window.Resources>
<StackPanel>
<controls:Button1 Style="{StaticResource Button1Style}" Background="Red" Text="Button 1" />
<controls:Button1 Style="{StaticResource Button1Style}" Background="Blue" Text="Button 2" />
</StackPanel>
When run, the content for Button 1 goes missing while Button 2 looks just fine. Removing Button 2 from the Window causes Button 1 to look as expected.
And as mentioned earlier, SNOOP indicates that Button 1's content is just not there when both buttons are present.
Any ideas?
I'm going to throw in a dissenting opinion here, starting with a quote from Matthew MacDonalds "Pro WPF in C#":
Custom controls are still a useful way to build custom widgets that
you can share between applications, but they’re no longer a
requirement when you want to enhance and customize core controls. (To
understand how remarkable this change is, it helps to point out that
this book’s predecessor, Pro .NET 2.0 Windows Forms and Custom
Controls in C#, had nine complete chapters about custom controls and
additional examples in other chapters. But in this book, you’ve made
it to Chapter 18 without a single custom control sighting!)
Put simply, there is just no need to be creating extra button classes just to control properties that already exist in the templates. You can do that just as easily with data binding or attached properties etc and it will be a lot more compatible with tools like Blend.
To illustrate the point here's a helper class for the two properties you're exposing in your sample code:
public static class ButtonHelper
{
public static double GetCornerRadius(DependencyObject obj)
{
return (double)obj.GetValue(CornerRadiusProperty);
}
public static void SetCornerRadius(DependencyObject obj, double value)
{
obj.SetValue(CornerRadiusProperty, value);
}
// Using a DependencyProperty as the backing store for CornerRadius. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.RegisterAttached("CornerRadius", typeof(double), typeof(ButtonHelper), new PropertyMetadata(0.0));
public static string GetButtonText(DependencyObject obj)
{
return (string)obj.GetValue(ButtonTextProperty);
}
public static void SetButtonText(DependencyObject obj, string value)
{
obj.SetValue(ButtonTextProperty, value);
}
// Using a DependencyProperty as the backing store for ButtonText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ButtonTextProperty =
DependencyProperty.RegisterAttached("ButtonText", typeof(string), typeof(ButtonHelper), new PropertyMetadata(""));
}
Now we can immediately create two style, one for each of your button types, that bind to these properties internally:
<Style x:Key="RoundedButtonStyle" TargetType="{x:Type Button}" >
<Setter Property="Margin" Value="10" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Foreground" Value="White" />
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="Background" Value="Red" />
<Setter Property="controls:ButtonHelper.CornerRadius" Value="4" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="{Binding Path=(controls:ButtonHelper.CornerRadius),
RelativeSource={RelativeSource TemplatedParent}}" BorderThickness="3"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" ContentSource="Content" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TextButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource RoundedButtonStyle}">
<Setter Property="BorderBrush" Value="Blue" />
<Setter Property="Background" Value="Blue" />
<Setter Property="controls:ButtonHelper.ButtonText" Value="TextButton" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="{Binding Path=(controls:ButtonHelper.CornerRadius),
RelativeSource={RelativeSource TemplatedParent}}" BorderThickness="3"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<TextBlock Text="{Binding Path=(controls:ButtonHelper.ButtonText),
RelativeSource={RelativeSource TemplatedParent}}" Background="Transparent" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
That's it! No custom control needed no need for x:Shared due to content being specified directly in a style, and it's a lot more light-weight. Here's an example of them being used:
<UniformGrid Columns="2">
<Button Style="{StaticResource RoundedButtonStyle}" Content="RoundedButton" />
<Button Style="{StaticResource RoundedButtonStyle}" Content="RoundedButton big radius" controls:ButtonHelper.CornerRadius="20"/>
<Button Style="{StaticResource TextButtonStyle}" />
<Button Style="{StaticResource TextButtonStyle}" controls:ButtonHelper.ButtonText="TextButton new text"/>
<Button Style="{StaticResource TextButtonStyle}" BorderBrush="Green" Background="Green"
controls:ButtonHelper.ButtonText="Both text and radius"
controls:ButtonHelper.CornerRadius="20" />
</UniformGrid>
And here's the result:
I do realize of course that I've specified the border in each template, but that too can be easily removed by placing a content control inside the border and using data templating to set the content.
What's happening is that the style actually has a single TextBlock instance. When the style is applied to the second button the TextBlock is actually re-parented to the new control. You should be able to avoid this by setting x:Shared="false" on the TextBlock element.

How to access a styles resources and retrieve an inner style that doesn't have a key

I have a style for a stack panel which has a key, within the stack panel I have buttons which have a default style set via the stack panel's Style > Resources > Styles, for this reason the buttons style is not set via a key but instead is set as it is a child of the stack panel! I'm sure this is a little tricky to understand as it sure feels tricky to explain... here is the style code...
<Style x:Key="VerticalMenuPanel" TargetType="{x:Type StackPanel}">
<Setter Property="Background" Value="#115e9a" />
<Style.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="Foreground" Value="White" />
<Setter Property="FontSize" Value="13" />
<Setter Property="FontFamily" Value="Calibri" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Height" Value="27" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border BorderBrush="White" BorderThickness="0,0,0,1">
<Border BorderBrush="#0160a2" BorderThickness="0,0,0,1.5">
<ContentPresenter VerticalAlignment="Center" />
</Border>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
</Style>
So, basically, without getting into what I'm doing too much I require the need to change the style of the buttons within the stack panel based on certain events. In order to change the style I simply find the resource based on the style key! HOWEVER here is my problem, I can't reset the style back to the "default" style provided by the parent the "VerticalMenuPanel"s Style.Resources as I don't know how to retrieve the style without having a definitive key for it. The obvious thing to do would be to give the button style a key but then I would have to explicitly define the style for all of the buttons instead of the style being applied by default as it is a child of the stack panel!
The bottom line is how do I retrieve a style without a key from within a parent styles resources (programatically)? Obviously I can retrieve the parent style via its key.
I hope you understand the issue I am having, and please feel free to let me know if I can explain anything better, add more clarity or if you wish edit the post yourself :)
When the Key is not provided for a style, the TargetType becomes the key of that style.
Here is an example:
<Grid x:Name="layoutRoot">
<Grid.Resources>
<Style TargetType="Button">
<Setter Property="Width" Value="100" />
</Style>
</Grid.Resources>
<Button />
</Grid>
then in the code-behind you can retrieve the default style using the Button type as the resource Key:
Style buttonStyle = layoutRoot.FindResource(typeof(Button)) as Style;

How to make a WPF resource be recalculated when a trigger's run?

In short: I've got a Style. It uses TemplateBinding a fair bit to make it parametrized instead of repeating myself over and over again. However, when a trigger for that style gets used and a resource gets used in a setter in that trigger, it just doesn't show up! Not even the default value gets shown. Here's a small program that replicates this issue:
TestDictionary.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:MyNamespace">
<Style TargetType="Button" x:Key="BtnTest">
<Style.Resources>
<Label Content="{TemplateBinding lcl:TestClass.String}" x:Key="innerLabel"/>
</Style.Resources>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Content" Value="{DynamicResource innerLabel}"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
MainWindow.xaml
<Window x:Class="MyNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:MyNamespace"
Title="Test" Width="500" Height="350">
<Window.Resources>
<ResourceDictionary Source="TestDictionary.xaml"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Content="Enable/Disable" Click="Click"/>
<Button Grid.Column="1" x:Name="btn" Style="{DynamicResource BtnTest}" lcl:TestClass.String="TESTING"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace MyNamespace
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Click(object sender, RoutedEventArgs e)
{
btn.IsEnabled = !btn.IsEnabled;
}
}
public class TestClass
{
public static string GetString(DependencyObject obj)
{
return (string)obj.GetValue(StringProperty);
}
public static void SetString(DependencyObject obj, string value)
{
obj.SetValue(StringProperty, value);
}
public static readonly DependencyProperty StringProperty =
DependencyProperty.RegisterAttached("String", typeof(string), typeof(TestClass), new PropertyMetadata("Default!"));
}
}
Instead of using a TemplateBinding, I also tried this:
{Binding Path=lcl:TestClass.String, RelativeSource={RelativeSource AncestorType={x:Type Button}}}
It still didn't work.
I know I'm probably doing something wrong, but the question is: what is it?
All you really need to make this work is to use RelativeSource in your binding. Since you are setting the attached property on the Button, in your style trigger, you can just bind to the attached property on self:
<Style TargetType="Button" x:Key="BtnTest">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Content"
Value="{Binding Path=(lcl:TestClass.String), RelativeSource={RelativeSource Self}}"/>
</Trigger>
</Style.Triggers>
</Style>
One cool thing about using your approach, since Button is a ContentControl, you're attached property can be any object, not just strings.
And to clarify what went wrong in your previous approach -
As others have said, TemplateBinding only works in ControlTemplates. It also only works when the DependencyProperty is defined on the class you are creating the template for (so you can never do a TemplateBinding to Grid.Row for example)
When binding to an attached property, the whole thing needs to be in parentheses, otherwise WPF will try to bind to a property of a property. Otherwise your RelativeSource binding was close!
I think if you want to have a Label inside the Button as the content, it may work (I didn't test that), but it doesn't seem like the best idea, as your Button can host any object you want.
EDIT for more complex example
So, if you need to display more than one dynamic property, I would recommend using a DataTemplate:
<Style TargetType="Button" x:Key="BtnTest">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Label Content="{Binding Path=(lcl:TestClass.String), RelativeSource={RelativeSource AncestorType={x:Type Button}}}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
Also, I want to point out that a DataTemplateSelector might be more applicable if you have multiple different criteria for changing the look of the content.
Now I see the details. What you should write before relative source is like:
Binding Path=(lcl:TestClass.String)
Do not forget to add parenthesis.
Your example does not work because TemplateBinding only works in a ControlTemplate. To achieve something akin to a TemplateBinding in Resources you need to do other stuff. Here's an example.
In order for TemplateBinding to work, you need to fix the code a little bit (this is just an example with no resources):
<Style x:Key="BtnTest" TargetType="{x:Type Button}">
<Setter Property="MinHeight" Value="100" />
<Setter Property="MinWidth" Value="200" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderBrush" Value="Blue" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2" Background="{TemplateBinding Background}">
<ContentPresenter RecognizesAccessKey="True" Content="{TemplateBinding lcl:TestClass.String}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</Style.Triggers>
</Style>
Useful links about this topic: Here, and here too.
EDIT:
You can also use the application settings instead of TestClass. Open "Project -> Properties: MyNamespace... -> Settings" and add your settings:
Name--------Type--------Scope--------Value
LabelText---string--------User----------Default
Set the your value for the LabelText in code. For example:
public MainWindow()
{
InitializeComponent();
MyNamespace.Properties.Settings.Default.LabelText = "Testing";
}
And use this ResourceDictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:properties="clr-namespace:MyNamespace.Properties"
xmlns:lcl="clr-namespace:MyNamespace">
<Style TargetType="Button" x:Key="BtnTest">
<Style.Resources>
<Label x:Key="innerLabel" Content="{Binding Source={x:Static properties:Settings.Default}, Path=LabelText, Mode=TwoWay}" />
</Style.Resources>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Content" Value="{DynamicResource innerLabel}"/>
</Trigger>
</Style.Triggers>
</Style>

WPF ControlTemplate for scrolling TreeView Control

I'm using a the TreeView control and it scrolls automatically to left-align TreeViewItem when one of them is clicked. I've gone looking at my Styles and ControlTemplates, but I haven't found anything. Is there a default ControlTemplate that causes this? I want to disable it.
The items scroll because the ScrollViewer calls BringIntoView() on them. So one way to avoid scrolling is to suppress the handling of the RequestBringIntoView event. You can try that out quickly by subclassing TreeView and instantiating this control instead:
public class NoScrollTreeView : TreeView
{
public class NoScrollTreeViewItem : TreeViewItem
{
public NoScrollTreeViewItem() : base()
{
this.RequestBringIntoView += delegate (object sender, RequestBringIntoViewEventArgs e) {
e.Handled = true;
};
}
protected override DependencyObject GetContainerForItemOverride()
{
return new NoScrollTreeViewItem();
}
}
protected override DependencyObject GetContainerForItemOverride()
{
return new NoScrollTreeViewItem();
}
}
after spending some hours on this problem i found a solution that works for me.
brians solution to prevent the RequestBringIntoView event on a TreeViewItem from bubbling was the first step. unfortunately this also stops a treeviewitem to be shown if you change the selected item programmatically by
yourtreeview.SelectedItem = yourtreeviewitem
so, for me the solution is to modify the controltemplate of the treeview as follows:
<Style x:Key="{x:Type TreeView}" TargetType="TreeView">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeView">
<Border Name="Border" BorderThickness="0" Padding="0" Margin="1">
<ScrollViewer Focusable="False" CanContentScroll="False" Padding="0">
<Components:AutoScrollPreventer Margin="0">
<ItemsPresenter/>
</Components:AutoScrollPreventer>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
the "autoscrollpreventer" is:
using System;
using System.Windows;
using System.Windows.Controls;
namespace LiveContext.Designer.GUI.Components {
public class AutoScrollPreventer : StackPanel
{
public AutoScrollPreventer() {
this.RequestBringIntoView += delegate(object sender, RequestBringIntoViewEventArgs e)
{
// stop this event from bubbling so that a scrollviewer doesn't try to BringIntoView..
e.Handled = true;
};
}
}
}
hope it helps..
It looks like I found a good clue on MSDN:
Sounds like this is an interaction
with the scrollviewer and the focus
system.
When an element is focused within a
ScrollViewer (which is part of the
TreeView template), the ScrollViewer
is instructed to make the element
visible. It automatically responds by
scrolling to the requested element.
The methods inside of ScrollViewer
that handle these focus requests are
all private and / or internal so you
really can't get to them. I don't
think there's too much you can do in
this case; it's just how focus works.
So, is that it? Surely there's a way to modify the TreeView template so that the ScrollViewer won't have this behavior...
Ok, I was finally able to get the default style like this:
using (Stream sw = File.Open(#"C:\TreeViewDefaults.xaml", FileMode.Truncate, FileAccess.Write))
{
Style ts = Application.Current.FindResource(typeof(TreeView)) as Style;
if (ts != null)
XamlWriter.Save(ts, sw);
}
Which produced:
<Style TargetType="TreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style.Triggers>
<Trigger Property="VirtualizingStackPanel.IsVirtualizing">
<Setter Property="ItemsControl.ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate><VirtualizingStackPanel IsItemsHost="True" /></ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Trigger.Value>
<s:Boolean>True</s:Boolean>
</Trigger.Value>
</Trigger>
</Style.Triggers>
<Style.Resources>
<ResourceDictionary />
</Style.Resources>
<Setter Property="Panel.Background">
<Setter.Value><DynamicResource ResourceKey="{x:Static SystemColors.WindowBrushKey}" /></Setter.Value>
</Setter>
<Setter Property="Border.BorderBrush">
<Setter.Value><SolidColorBrush>#FF828790</SolidColorBrush></Setter.Value>
</Setter>
<Setter Property="Border.BorderThickness">
<Setter.Value><Thickness>1,1,1,1</Thickness></Setter.Value>
</Setter>
<Setter Property="Control.Padding">
<Setter.Value><Thickness>1,1,1,1</Thickness></Setter.Value>
</Setter>
<Setter Property="TextElement.Foreground">
<Setter.Value><DynamicResource ResourceKey="{x:Static SystemColors.ControlTextBrushKey}" /></Setter.Value>
</Setter>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility">
<Setter.Value><x:Static Member="ScrollBarVisibility.Auto" /></Setter.Value>
</Setter>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility">
<Setter.Value><x:Static Member="ScrollBarVisibility.Auto" /></Setter.Value>
</Setter>
<Setter Property="Control.VerticalContentAlignment">
<Setter.Value><x:Static Member="VerticalAlignment.Center" /></Setter.Value>
</Setter>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="TreeView">
<Border BorderThickness="{TemplateBinding Border.BorderThickness}"
BorderBrush="{TemplateBinding Border.BorderBrush}"
Name="Bd" SnapsToDevicePixels="True">
<ScrollViewer CanContentScroll="False"
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
Background="{TemplateBinding Panel.Background}"
Padding="{TemplateBinding Control.Padding}"
Name="_tv_scrollviewer_"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"
Focusable="False">
<ItemsPresenter />
</ScrollViewer>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="UIElement.IsEnabled">
<Setter Property="Panel.Background" TargetName="Bd">
<Setter.Value>
<DynamicResource ResourceKey="{x:Static SystemColors.ControlBrushKey}" />
</Setter.Value>
</Setter>
<Trigger.Value>
<s:Boolean>False</s:Boolean>
</Trigger.Value>
</Trigger>
<Trigger Property="VirtualizingStackPanel.IsVirtualizing">
<Setter Property="ScrollViewer.CanContentScroll" TargetName="_tv_scrollviewer_">
<Setter.Value><s:Boolean>True</s:Boolean></Setter.Value>
</Setter>
<Trigger.Value>
<s:Boolean>True</s:Boolean>
</Trigger.Value>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Which, unfortunately, doesn't look helpful. I don't see any properties in there for stopping the auto-scroll-focus thing.
Still looking...
Another fun tidbit: there is a overridable boolean value called HandlesScrolling that always returns true. After decompiling the source, it looks like this property is NEVER used (or it's being used in some deep, dark, secret place in XAML). I tried making my own TreeView control to set this value to false and it didn't work.

Categories

Resources