Is it possible to change or modify a specific part of a control template without having to recreate the entire control template of the control in the xaml?
For example, I was trying to get rid of the border of a textbox, so I could throw together a basic search box with rounded corners (example xaml below). Setting the borderthickness to 0 works fine, until you mouse over the textbox and a pseudo border they added to the control flashes up. If I look at the controltemplate for the textbox, I can even see the visual state is named, but cannot think of how to disable it.
Without overriding the control template of the TextBox, how would I stop the Visual State Manager firing the mouse over effect on the TextBox?
<Border Background="White" CornerRadius="10" VerticalAlignment="Center" HorizontalAlignment="Center" BorderThickness="3" BorderBrush="#88000000">
<Grid VerticalAlignment="Center" HorizontalAlignment="Center" Width="200" Margin="5,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Path Height="13" Width="14" Stretch="Fill" Stroke="#FF000000" StrokeThickness="2" Data="M9.5,5 C9.5,7.4852815 7.4852815,9.5 5,9.5 C2.5147185,9.5 0.5,7.4852815 0.5,5 C0.5,2.5147185 2.5147185,0.5 5,0.5 C7.4852815,0.5 9.5,2.5147185 9.5,5 z M8.5,8.4999971 L13.5,12.499997" />
<TextBox GotFocus="TextBox_GotFocus" Background="Transparent" Grid.Column="1" BorderThickness="0" Text="I am searchtext" Margin="5,0,5,0" HorizontalAlignment="Stretch" />
</Grid>
</Border>
I've found a way to do this, by inheriting off the control and overriding the OnApplyTemplate. It's not ideal, but I think it's better than having to copy the entire control template. Here's an example of creating a borderless textbox, essentially disabling the mouse over visual state by always clearing the storyboard:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
namespace SilverlightTestApplication
{
public class BorderlessTextBox : TextBox
{
public BorderlessTextBox()
{
BorderThickness = new System.Windows.Thickness(0);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
//Get the mouse over animation in the control template
var MouseOverVisualState = GetTemplateChild("MouseOver") as VisualState;
if (MouseOverVisualState == null)
return;
//get rid of the storyboard it uses, so the mouse over does nothing
MouseOverVisualState.Storyboard = null;
}
}
}
Its been awhile since I used XAML, but no, I don't believe you can just modify a piece of the template.
However if you have the IDE you can create a copy of the currently applied template and just modify the piece you want and leave the rest as is. See the How to Edit section of this link.
Retrieve the default template of every control, with the XAML reader, then copy/paste and modify what you want... not very clean but I think this is the only way (I'm searching how to retrieve this default template)
In WPF, not sure about silverlight here a snippet of code to retrieve the template of Aero, you can try to copy/paste and change what you want:
public Window1()
{
InitializeComponent();
using(FileStream fs = new FileStream("C:/TextBoxTemplate.xaml", FileMode.Create))
{
var res = LoadThemeDictionary(typeof(TextBox), "Aero", "NormalColor");
var buttonRes = res[typeof(TextBox)];
XamlWriter.Save(buttonRes, fs);
}
}
public static ResourceDictionary LoadThemeDictionary(Type t,
string themeName, string colorScheme)
{
Assembly controlAssembly = t.Assembly;
AssemblyName themeAssemblyName = controlAssembly.GetName();
object[] attrs = controlAssembly.GetCustomAttributes(
typeof(ThemeInfoAttribute), false);
if(attrs.Length > 0)
{
ThemeInfoAttribute ti = (ThemeInfoAttribute)attrs[0];
if(ti.ThemeDictionaryLocation ==
ResourceDictionaryLocation.None)
{
// There are no theme-specific resources.
return null;
}
if(ti.ThemeDictionaryLocation ==
ResourceDictionaryLocation.ExternalAssembly)
{
themeAssemblyName.Name += "." + themeName;
}
}
string relativePackUriForResources = "/" +
themeAssemblyName.FullName +
";component/themes/" +
themeName + "." +
colorScheme + ".xaml";
Uri resourceLocater = new System.Uri(
relativePackUriForResources, System.UriKind.Relative);
return Application.LoadComponent(resourceLocater)
as ResourceDictionary;
}
I 've never used Silverlight, but I don't think there is a lot of things to do to adapt this template to Silverlight.
Source : Default template in WPF
Related
I have a situation where I need to create View box with one button. The xaml for this is as below: Please observe Width property of viewbox. The Width should be increased/decreased according to a slider bar(moving to right increases it, to left decreases it). As listed below I know how to do it in xaml and it works fine. But my requirement is to be able to create viewbox in code behind and assign it the properties.
<WrapPanel x:Name="_wrpImageButtons" Grid.IsSharedSizeScope="True"
ScrollViewer.CanContentScroll="True" d:LayoutOverrides="Height"
Margin="5">
<Viewbox x:Name="_ScaleButton"
Width="{Binding Value, ElementName=ZoomSlider}" Stretch="Fill">
<CustomButton:_uscVCARSImagesButton x:Name="_btnImage1"/>
</Viewbox>
</WrapPanel>
Thanks.
This should do what you want:
Viewbox x = new Viewbox();
Binding bnd = new Binding("Value") { ElementName = "ZoomSlider"};
BindingOperations.SetBinding(x, Viewbox.WidthProperty, bnd);
// ... Code to insert the Viewbox into the WrapPanel etc.
You can create the binding relatively easily in Code Behind:
var widthBinding = new Binding("Value") { ElementName = "ZoomSlider" };
_ScaleButton.SetBinding(FrameworkElement.WidthProperty, widthBinding);
I have a user control in which I have a popup that occasionally hides the rest of the control. Something like this:
<Grid>
<Grid Name="myGrid">
<!-- Some stuff -->
</Grid>
<Popup Width="{Binding ElementName=myGrid, Path=ActualWidth}"
PlacementTarget="{Binding ElementName=myGrid}"
Placement="Relative">
<Border Name="popupBorder">
<Grid>
<!--- Slightly different stuff --->
<Grid>
</Border>
</Popup>
</Grid>
...and that works nicely except of course I need to set a background. I would like the popup-hides-everything transition to be as seamless as possible so it'd be nice if my popup and the rest of my control shared the same background.
I have been unable to figure out how to do this when the usercontrol doesn't have a background explicitly set.
I have tried this (in XAML):
<Border Name="popupBorder" Background="{Binding ElementName=myGrid, Path=Background}" />
I have tried this (in code):
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var brush = this.GetValue(UserControl.BackgroundProperty);
popupBorder.Background = (Brush)brush;
}
In all cases the border's background is 'null' and the popup is just a big black block. Is there a way to do this?
(In case you want to tell me that there's just a better way to do what I'm trying to do ... I have a long list; I want to display the first few items with a "See more" button; the whole mess is in an ItemsControl and I don't want the "See more" button to resize the items - hence, show the long list on a popup.)
After getting some advice from dkozl I was able to implement the following solution:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
puBorder.Background = findBackground(this) ?? SystemColors.ControlBrush;
}
private Brush findBackground(DependencyObject element)
{
var parent = element;
while (parent != null)
{
var p = parent as Control;
if (p != null && p.Background != null)
return p.Background;
parent = VisualTreeHelper.GetParent(parent);
}
return null;
}
I tried to do the same thing in a converter but the converter executes before the control is added to the visual tree (I assume, anyway - it didn't have a visual parent) and so it wasn't useful.
I am using the following code to move the text in TextBlock from right to left:-
ThicknessAnimation ThickAnimation = new ThicknessAnimation();
ThickAnimation.From = new Thickness(0, 0, 0, 0);
ThickAnimation.To = new Thickness(-TextGraphicalWidth, 0, 0, 0);
ThickAnimation.RepeatBehavior = RepeatBehavior.Forever;
ThickAnimation.Duration = new Duration(TimeSpan.FromSeconds(40);
TextBlockMarquee.BeginAnimation(TextBlock.MarginProperty, ThickAnimation);
The problem I am having is this code works perfectly for a text whose width is less then that of screen. But for text whose width is greater then screen the text flickers a lot(Flickering increases when text contains unicode characters like: "\u25Bc").
here:-
TextGraphicalWidth is width of original text.
TextLenghtGraphicalWidth is width of text after appending text to original text.
How can I handle this?
Xaml:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Canvas ClipToBounds="True" Name="canMain" Background="Transparent">
<Grid Width="{Binding ElementName=canMain, Path=ActualWidth}" >
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Name="TextBlockMarquee" Text="This is my animated text with special characters:" />
<Border Grid.Row="1" BorderBrush="Black" BorderThickness="1">
<TextBlock ClipToBounds="True" FontSize="22" Name="TextBoxMarquee" FontFamily="Segoe UI" Text="" />
</Border>
<TextBlock Grid.Row="2" Name="TextBlockMarqueenew" Text="This is my animated text without special characters:" />
<Border Grid.Row="3" BorderBrush="Black" BorderThickness="1">
<TextBlock ClipToBounds="True" FontSize="22" Name="TextBoxMarquee1" FontFamily="Segoe UI" Text="" />
</Border>
</Grid>
</Canvas>
</Grid>
Code Behind:-
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace TickerDemoNew
{
/// <summary>
/// Interaction logic for TickerMaxTextBlock.xaml
/// </summary>
public partial class TickerMaxTextBlock : Window
{
StringBuilder s,s1;
string TexttoScroll, TexttoScroll1;
public TickerMaxTextBlock()
{
InitializeComponent();
TextOptions.SetTextFormattingMode(TextBoxMarquee,TextFormattingMode.Ideal);
UseLayoutRounding = true;
TextBoxMarquee.Inlines.Add(new Run("I have attached herewith the full implementation of Custom TextBox class (TextBoxEx) in a word file. In the implementation when Tooltip is opening, it is checking whether text has truncated or not. If text has not truncated then tooltip won’t be displayed (as it is not necessary because full text is anyway visible). Also, in order to show the tooltip, text property of the Textblock should be bound with the tooltip. ") { Foreground = Brushes.Red });
TextBoxMarquee.Inlines.Add(new Run(" Despite of all the interop support available sometimes you will still come across some tricky issues which will be difficult to overcome. One such problem I faced recently. I had to configure the Dialog with MFC PropertySheet control. This PropertySheet control was holding few Property pages. These property pages didn’t had any MFC controls as content but hosting the user controls developed in WPF. First I will write very briefly about how to load the WPF user control inside the MFC container and then I will explain about my tricky problem and how I fixed it.") { Foreground = Brushes.Green });
}
private void Window_Loaded_1(object sender, RoutedEventArgs e)
{
double TextGraphicalWidth = 0;
foreach (var str in TextBoxMarquee.Inlines)
{
TextGraphicalWidth = TextGraphicalWidth + new FormattedText(((Run)str).Text, System.Threading.Thread.CurrentThread.CurrentCulture, System.Windows.FlowDirection.LeftToRight, new Typeface(str.FontFamily.ToString()), str.FontSize, str.Foreground).WidthIncludingTrailingWhitespace;
}
double TextLenghtGraphicalWidth = 0;
while (TextLenghtGraphicalWidth < TextBoxMarquee.ActualWidth)
{
TextBoxMarquee.Inlines.Add(new Run("I have attached herewith the full implementation of Custom TextBox class (TextBoxEx) in a word file. In the implementation when Tooltip is opening, it is checking whether text has truncated or not. If text has not truncated then tooltip won’t be displayed (as it is not necessary because full text is anyway visible). Also, in order to show the tooltip, text property of the Textblock should be bound with the tooltip. ") { Foreground = Brushes.Red });
TextBoxMarquee.Inlines.Add(new Run(" Despite of all the interop support available sometimes you will still come across some tricky issues which will be difficult to overcome. One such problem I faced recently. I had to configure the Dialog with MFC PropertySheet control. This PropertySheet control was holding few Property pages. These property pages didn’t had any MFC controls as content but hosting the user controls developed in WPF. First I will write very briefly about how to load the WPF user control inside the MFC container and then I will explain about my tricky problem and how I fixed it.") { Foreground = Brushes.Green });
foreach (var str in TextBoxMarquee.Inlines)
{
TextLenghtGraphicalWidth = TextLenghtGraphicalWidth + new FormattedText(((Run)str).Text, System.Threading.Thread.CurrentThread.CurrentCulture, System.Windows.FlowDirection.LeftToRight, new Typeface(str.FontFamily.ToString()), str.FontSize, str.Foreground).WidthIncludingTrailingWhitespace;
}
}
TextBoxMarquee.Inlines.Add(new Run("I have attached herewith the full implementation of Custom TextBox class (TextBoxEx) in a word file. In the implementation when Tooltip is opening, it is checking whether text has truncated or not. If text has not truncated then tooltip won’t be displayed (as it is not necessary because full text is anyway visible). Also, in order to show the tooltip, text property of the Textblock should be bound with the tooltip. ") { Foreground = Brushes.Red });
TextBoxMarquee.Inlines.Add(new Run(" Despite of all the interop support available sometimes you will still come across some tricky issues which will be difficult to overcome. One such problem I faced recently. I had to configure the Dialog with MFC PropertySheet control. This PropertySheet control was holding few Property pages. These property pages didn’t had any MFC controls as content but hosting the user controls developed in WPF. First I will write very briefly about how to load the WPF user control inside the MFC container and then I will explain about my tricky problem and how I fixed it.") { Foreground = Brushes.Green });
double totalWidth = 0;
foreach (var newStr in TextBoxMarquee.Inlines)
{
totalWidth = totalWidth + new FormattedText(((Run)newStr).Text, System.Threading.Thread.CurrentThread.CurrentCulture, System.Windows.FlowDirection.LeftToRight, new Typeface(newStr.FontFamily.ToString()), newStr.FontSize, newStr.Foreground).WidthIncludingTrailingWhitespace;
}
MessageBox.Show((TextLenghtGraphicalWidth + TextBoxMarquee.ActualWidth).ToString());
MessageBox.Show((TextLenghtGraphicalWidth + TextGraphicalWidth).ToString());
MessageBox.Show(totalWidth.ToString());
ThicknessAnimation ThickAnimation = new ThicknessAnimation();
ThickAnimation.From = new Thickness(0, 0, 0, 0);
ThickAnimation.To = new Thickness(-TextGraphicalWidth, 0, 0, 0);
ThickAnimation.RepeatBehavior = RepeatBehavior.Forever;
ThickAnimation.Duration = new Duration(TimeSpan.FromSeconds((50 * (totalWidth)) / TextBoxMarquee.ActualWidth));
TextBoxMarquee.BeginAnimation(TextBlock.MarginProperty, ThickAnimation);
}
}
}
my problem is the following: In my program I let the user place shapes (class DrawingShape) on a Canvas. The Drawing Shape encapsulates a stacked path and label:
<UserControl x:Class="HandballTrainerFluent.Graphics.DrawingShape"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="60"
d:DesignWidth="60"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid x:Name="container" Width="Auto" Height="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="38"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Canvas x:Name="geometryCanvas" HorizontalAlignment="Center" Grid.Row="0" Width="38" Height="38">
<Path x:Name="Path"
Width="35.8774"
Height="31.2047"
Canvas.Left="1.0613"
Canvas.Top="3.29528"
Stretch="Fill"
StrokeLineJoin="Round"
Stroke="{Binding OutlineBrush,Mode=OneWay}"
StrokeThickness="{Binding OutlineWidth,Mode=OneWay}"
StrokeDashArray="{Binding OutlinePattern,Mode=OneWay}"
Fill="{Binding FillBrush,Mode=OneWay}"
Data="F1 M 19,3.79528L 1.5613,34L 36.4387,34L 19,3.79528 Z "/>
</Canvas>
<TextBlock x:Name="TextBox" HorizontalAlignment="Center" Grid.Row="1" Text="{Binding LabelText,Mode=OneWay}"></TextBlock>
</Grid>
</UserControl>
So some visual setting and the label text are bound to Properties of the code-behind file.
After deserializing a Canvas with these Drawing shapes, I need to restore the binding between the XAML and the code-behind file. I've tried this, but it does not seem to work:
private void RepairBindingsAfterLoading()
{
foreach (UIElement element in this.drawingCanvas.Children)
{
if (element.GetType() == typeof(DrawingShape))
{
DrawingShape shape = (DrawingShape)element;
BindingOperations.ClearAllBindings(shape.Path);
BindingOperations.ClearAllBindings(shape.TextBox);
BindingOperations.ClearAllBindings(shape);
shape.BeginInit();
Binding dataContextBinding = new Binding();
dataContextBinding.RelativeSource = RelativeSource.Self;
shape.SetBinding(DrawingShape.DataContextProperty, dataContextBinding);
Binding fillBinding = new Binding("FillBrush");
shape.Path.SetBinding(Path.FillProperty, fillBinding);
Binding outlineBinding = new Binding("OutlineBrush");
shape.Path.SetBinding(Path.StrokeProperty, outlineBinding);
Binding widthBinding = new Binding("OutlineWidth");
shape.Path.SetBinding(Path.StrokeThicknessProperty, widthBinding);
Binding patternBinding = new Binding("OutlinePattern");
shape.Path.SetBinding(Path.StrokeDashArrayProperty, patternBinding);
Binding labelTextBinding = new Binding("LabelText");
shape.TextBox.SetBinding(TextBlock.TextProperty, labelTextBinding);
shape.EndInit();
shape.UpdateLayout();
}
}
}
No matter what I do to the code-behind Properties (e.g. change FillBrush), the visuals of the displayed DrawingShape won't update. Am I missing an important step here?
I've added shape.BeginUpdate() and shape.EndUpdate() after seeing this question: Bindings not applied to dynamically-loaded xaml
Thanks a lot for any insights
Edit 2012-09-25
Looking at another piece of code which does not depend on any bindings makes me wonder, if I can actually reference any elements from the Xaml-Definition via their x:Name after de-serialization. The following callback does not do anything on a shape:
private void rotateClockwiseMenuItem_Click(object sender, RoutedEventArgs e)
{
if(this.drawingCanvas.SelectedItem.GetType() == typeof(DrawingShape))
{
DrawingShape shape = (DrawingShape)this.drawingCanvas.SelectedItem;
TransformGroup transformStack = new TransformGroup();
transformStack.Children.Add(shape.geometryCanvas.LayoutTransform);
transformStack.Children.Add(new RotateTransform(90));
shape.geometryCanvas.LayoutTransform = transformStack;
}
}
Debugging tells me that the contents of shape seem just right. When I execute the command once, shape.geometryCanvas.LayoutTransformis the identity matrix. When executing it a second time, shape.geometryCanvas.LayoutTransform is a TransformGroup of two elements.
It somehow looks like the reference for geometryCanvas (declared in the Xaml) is no the one used on screen.
Got it!
I didn't know that you can't successfully reference x:Name'd XAML elements from outside the code-behind file after de-serialization (that at least seems to be the problem at hand).
A solution is to use FindName() on the UserControl, e.g.:
TextBlock textBox = shape.FindName("TextBox") as TextBlock;
The complete and correct RepairBindingsAfterLoading() looks like this:
private void RepairBindingsAfterLoading()
{
foreach (UIElement element in this.drawingCanvas.Children)
{
if (element.GetType() == typeof(DrawingShape))
{
DrawingShape shape = (DrawingShape)element;
shape.DataContext = shape;
Path path = shape.FindName("Path") as Path;
Binding fillBinding = new Binding("FillBrush");
path.SetBinding(Path.FillProperty, fillBinding);
Binding outlineBinding = new Binding("OutlineBrush");
path.SetBinding(Path.StrokeProperty, outlineBinding);
Binding widthBinding = new Binding("OutlineWidth");
path.SetBinding(Path.StrokeThicknessProperty, widthBinding);
Binding patternBinding = new Binding("OutlinePattern");
path.SetBinding(Path.StrokeDashArrayProperty, patternBinding);
TextBlock textBox = shape.FindName("TextBox") as TextBlock;
Binding labelTextBinding = new Binding("LabelText");
textBox.SetBinding(TextBlock.TextProperty, labelTextBinding);
}
}
}
Just for the record, my clumsy
BindingOperations.ClearAllBindings(shape.Path);
BindingOperations.ClearAllBindings(shape.TextBox);
BindingOperations.ClearAllBindings(shape);
works just like the much more simple and elegant solution suggested by dbaseman with:
shape.DataContext = this;
Hope this helps someone else to avoid my mistake :-)
I have a situation where I need to create View box with one button. The xaml for this is as below: Please observe Width property of viewbox. The Width should be increased/decreased according to a slider bar(moving to right increases it, to left decreases it). As listed below I know how to do it in xaml and it works fine. But my requirement is to be able to create viewbox in code behind and assign it the properties.
<WrapPanel x:Name="_wrpImageButtons" Grid.IsSharedSizeScope="True"
ScrollViewer.CanContentScroll="True" d:LayoutOverrides="Height"
Margin="5">
<Viewbox x:Name="_ScaleButton"
Width="{Binding Value, ElementName=ZoomSlider}" Stretch="Fill">
<CustomButton:_uscVCARSImagesButton x:Name="_btnImage1"/>
</Viewbox>
</WrapPanel>
Thanks.
This should do what you want:
Viewbox x = new Viewbox();
Binding bnd = new Binding("Value") { ElementName = "ZoomSlider"};
BindingOperations.SetBinding(x, Viewbox.WidthProperty, bnd);
// ... Code to insert the Viewbox into the WrapPanel etc.
You can create the binding relatively easily in Code Behind:
var widthBinding = new Binding("Value") { ElementName = "ZoomSlider" };
_ScaleButton.SetBinding(FrameworkElement.WidthProperty, widthBinding);