WPF text box animated text characters change - wpf

I am trying to make a text-box, where whenever any new character appears/disappears, it is somehow highlighted (eg. the character appears with yellow to black font-color fade in gradient, ...).
In my case, when I write a text in the first text-box, I expect ONLY NEW CHARACTERS in the second text-box to be highlighted, when they appears. Lastly I need to have both texts to be char by char aligned.
Unfortunetaly text-box text property is seen only as one property of the whole string, so when I tried to add animations after TextBox.TextChanged event, but the whole text faded in after every keystroke. My only idea is to write some adapter, which transforms the string in the second text-box to collection of labels, where every label could act as a single character, so the highlighting animations could be performed on the selected individual labels (chars).
This is a minimal sub-issue of my project, which is written using the MVVM pattern. So ideally, I am seeking for solution in xaml, but I am also open to any hacking solution, since characters in the text-box is not designed to be animated.
Here, I include a code to reproduce the window in the example.
MainWindow
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="AnimatedTextBoxStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
<local:AnimatedTextBoxViewModel x:Key="ViewModel"/>
</ResourceDictionary>
</Window.Resources>
<StackPanel DataContext="{StaticResource ViewModel}" Margin="5,5,5,5">
<TextBox Margin="0,0,0,2"
Style="{StaticResource ResourceKey=AnimatedTextBoxStyle}"
Text="{Binding SomeText,
UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay}">
</TextBox>
<TextBox IsEnabled="False"
Style="{StaticResource ResourceKey=AnimatedTextBoxStyle}"
Text="{Binding SomeText}">
</TextBox>
</StackPanel>
AnimatedTextBoxStyle.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TextBoxCharsAnimation">
<Style x:Key="AnimatedTextBoxStyle" TargetType="{x:Type TextBox}">
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Height" Value="26"/>
<Style.Triggers>
<EventTrigger RoutedEvent="TextBox.TextChanged">
<!-- Maybe begin storyboard here? -->
</EventTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
AnimatedTextBoxViewModel.cs
using TextBoxCharsAnimation.Support;
namespace TextBoxCharsAnimation
{
class AnimatedTextBoxViewModel : ViewModelBase
{
private string _someText = "";
public string SomeText
{
get => _someText;
set
{
_someText = value;
OnPropertyChanged();
}
}
}
}

We cannot use TextBox control to change some specific character color. We need to use RichTextBox instead of second text box. RichTextBox as the name suggests is rich for customization. You need to hook up the PreviewTextInput event to get the typed char.
Below is the sample code which might need some modifications to suit your requirements.
private void inputRichTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
TextPointer start;
start = inputRichTextBox.CaretPosition;
TextPointer end = inputRichTextBox.Document.ContentEnd;
TextRange range = new TextRange(start, end);
range.ApplyPropertyValue(RichTextBox.ForegroundProperty, Brushes.Green);
}
For colour gradient requirement instead of Brushes.Green you need to replace it with similar to below code.
LinearGradientBrush myHorizontalGradient = new LinearGradientBrush();
myHorizontalGradient.StartPoint = new Point(0);
myHorizontalGradient.EndPoint = new Point(1);
myHorizontalGradient.GradientStops.Add(new GradientStop(Colors.Yellow, 0));
myHorizontalGradient.GradientStops.Add(new GradientStop(Colors.Black, 1));

Related

Cannot set margin on a custom UserControl in WPF

I have made a simple UserControl called SmallCtrl. Inside another UserControl called LargeCtrl, I dynamically add SmallCtrl to the list of its children using this code behind (for testing):
public void LargeCtrl_Loaded(object sender, EventArgs args)
{
for (int i = 0; i < 10; i++)
_StackPanel.Children.Add(new SmallCtrl());
}
I use StackPanel with horizontal orientation and I need its child UserControls to have a right margin to create space between them, here is the style resource:
<Grid>
<StackPanel x:Name="_StackPanel" Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="{x:Type customCtrls:SmallCtrl}">
<Setter Property="Margin" Value="0,0,10,0"/>
</Style>
</StackPanel.Resources>
</StackPanel>
</Grid>
However, when I load my Window with LargeCtrl the margin doesn't show. If I replace my SmallCtrl with a TextBox, everything works. What is my problem here?

Bind Canvas.Left and Canvas.Top in Style

I am trying to reflect a position from my view-model expressing the location of an object in my view by binding the Canvas.Left and Canvas.Top properties in the style of the item view to appropriate properties in the view-model. However, the bindings do not seem to work.
For this minimal sample, I have simplified the structure so there only is one control Thing that is styled and templated:
using System;
using System.Windows;
using System.Windows.Controls;
namespace LocationBinding
{
public class Thing : Control
{
static Thing()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Thing), new FrameworkPropertyMetadata(typeof(Thing)));
}
public Point Location {
get {
return new Point(70, 70);
}
}
public double VPos {
get {
return 100;
}
}
}
}
For the sake of simplicity, I have declared the style in the resource dictionary of the main window - a simple window with a canvas on it (my real project has this in Themes\Generic.xaml). In its style, I am binding to property values of the control:
<Window x:Class="LocationBinding.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LocationBinding"
Title="LocationBinding" Height="300" Width="300">
<Window.Resources>
<Style TargetType="local:Thing">
<Setter Property="Panel.ZIndex" Value="542"/>
<Setter Property="Canvas.Left" Value="{Binding Location.X}"/>
<Setter Property="Canvas.Top" Value="{Binding VPos}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Thing">
<Ellipse Fill="ForestGreen" Width="30" Height="30"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Canvas Name="cnv">
</Canvas>
</Window>
The code-behind of the main window simply adds a Thing instance to the canvas:
using System;
using System.Windows;
namespace LocationBinding
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
cnv.Children.Add(new Thing());
}
}
}
The style is correctly applied, as evidenced by the ZIndex value (according to Snoop) and the correct template-based appearance of the control. However, Canvas.Left and Canvas.Top remain unset (and thus the Thing sticks in the top left corner of the canvas), even though according to threads such as this or this, Property="Canvas.Left" seems to be the correct syntax to refer to the attached property in a style.
I was first trying to bind Canvas.Top to Location.Y and replaced that with the VPos property in case the problem is related to binding to struct properties, but that does not seem to change anything, either.
What am I missing; how can I bind Canvas.Left and Canvas.Top in my style to the coordinates from my Thing.Location property?
By default, binding will search for property in DataContext of control but Location is your control (Thing) property.
So you need to use RelativeSource with Mode set to Self to tell binding engine to search for property in control itself and not in DataContext of control:
<Setter Property="Canvas.Left" Value="{Binding Location.X,
RelativeSource={RelativeSource Self}}"/>

Load controls on runtime based on selection

I'm new to XAML and I have a case where I need to change controls based on a selection on a combobox with templates.
For example, let's say that a user selects a template that requires a day of week and a time range that something will be available. I would like that, on the moment of the selection, the control with the information needed get build on the screen and that the bindings get to work as well.
Can someone give me a hint or indicate an article with an elegant way to do so?
Thanks in advance.
The solution you are looking for is a ContentControl and DataTemplates. You use the selected item of the ComboBox to change ContentTemplate of the Content Control.
You question mentions binding so I will assume you understand the MVVM pattern.
As an example, lets use MyModel1 as the Model
public class MyModel1
{
private Collection<string> values;
public Collection<string> Values { get { return values ?? (values = new Collection<string> { "One", "Two" }); } }
public string Field1 { get; set; }
public string Field2 { get; set; }
}
And MyViewModel as the ViewModel
public class MyViewModel
{
public MyViewModel()
{
Model = new MyModel1();
}
public MyModel1 Model { get; set; }
}
And the code behind does nothing but instantiate the ViewModel.
public partial class MainWindow : Window
{
public MainWindow()
{
ViewModel = new MyViewModel();
InitializeComponent();
}
public MyViewModel ViewModel { get; set; }
}
All three are very simple classes. The fun comes in the Xaml which is
<Window x:Class="StackOverflow._20893945.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:this="clr-namespace:StackOverflow._20893945"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="MyModel1Template1" DataType="{x:Type this:MyModel1}">
<StackPanel>
<TextBlock Text="Template 1"></TextBlock>
<ComboBox ItemsSource="{Binding Path=Values}" SelectedItem="{Binding Path=Field1}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="MyModel1Template2" DataType="{x:Type this:MyModel1}">
<StackPanel>
<TextBlock Text="Template 2"></TextBlock>
<TextBox Text="{Binding Path=Field2}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="2">
<ComboBox x:Name="TypeSelector">
<system:String>Template 1</system:String>
<system:String>Template 2</system:String>
</ComboBox>
</StackPanel>
<ContentControl Content="{Binding Path=Model}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=TypeSelector, Path=SelectedItem}" Value="Template 2">
<Setter Property="ContentTemplate" Value="{StaticResource MyModel1Template2}" />
</DataTrigger>
</Style.Triggers>
<Setter Property="ContentTemplate" Value="{StaticResource MyModel1Template1}" />
</Style>
</ContentControl.Style>
</ContentControl>
</DockPanel>
</Window>
The notable points of the view are
The DataContext is initialised on the Window element, allowing for auto-complete on our binding expressions
The definition of 2 template to display 2 different views of the data.
The ComboBox is populated with a list of strings and has a default selection of the first element.
The ContentControl has its content bound to the Model exposed via the ViewModel
The default DataTemplate is the first template with a ComboBox.
The Trigger in the ContentControl's style will change the ContentTemplate if the SelectedItem of the ComboBox is changed to 'Template 2'
Implied facts are
If the SelectedItem changes back to 'Template 1', the style will revert the the ContentTemplate back to the default, ie MyModel1Template1
If there were a need for 3 separate displays, create another DataTemplate, add a string to the ComboBox and add another DataTrigger.
NOTE: This is the complete source to my example. Create a new C#/WPF project with the same classes and past the code in. It should work.
I hope this helps.

Attached behavior binding to element in controltemplate

I am adding an attached behaviour to a slider which will cause it to scroll some content when the thumb is dragged and held over a specific region. (Can't use a straightforward IsMouseOver trigger as the Slider Thumb has MouseCapture.)
The behaviour has 3 properties:
#region IsScrollHoverProperty
public static readonly DependencyProperty IsScrollHoverProperty = DependencyProperty.RegisterAttached(
"IsScrollHover",
typeof(Boolean),
typeof(ScrollHoverAreaBehaviour),
new UIPropertyMetadata(false));
#endregion
#region ScrollLeftRectProperty
public static readonly DependencyProperty ScrollLeftRectProperty = DependencyProperty.RegisterAttached(
"ScrollLeftRect",
typeof(Rectangle),
typeof(ScrollHoverAreaBehaviour),
new UIPropertyMetadata(null));
#endregion
#region ScrollRightRectProperty
public static readonly DependencyProperty ScrollRightRectProperty = DependencyProperty.RegisterAttached(
"ScrollRightRect",
typeof(Rectangle),
typeof(ScrollHoverAreaBehaviour),
new UIPropertyMetadata(null));
#endregion
The IsScrollHoverProperty is being set to true when the user drags the slider, this is all done in the Slider's ControlTemplates.Triggers, and works correctly.
When it's set to true the callback is going to hook PreviewMouseEnterHandlers into the two Rectangles to detect when the mouse enters them.
The Rectangles in question are also defined in the Slider's controltemplate thusly:
<StackPanel Grid.Row="0" Grid.RowSpan="3" HorizontalAlignment="Left" Orientation="Horizontal">
<Rectangle Width="40" Fill="#AAAAAAAA" Name="ScrollLeftRect"/>
</StackPanel>
<StackPanel Grid.Row="0" Grid.RowSpan="3" HorizontalAlignment="Right" Orientation="Horizontal">
<Rectangle Width="40" Fill="#AAAAAAAA" Name="ScrollRightRect"/>
</StackPanel>
The problem I have is binding these Rectangles to the attached ScrollRightRect and ScrollLeftRect Properties. I have tried a few things and suspect I have made a stupid binding error or am trying to do something not allowed. I am currently binding them in the controltemplate.triggers as follows:
<Trigger Property="local:ScrollHoverAreaBehaviour.IsScrollHover" Value="False">
<Setter Property="local:ScrollHoverAreaBehaviour.ScrollLeftRect" Value="{Binding ElementName=ScrollLeftRect}"/>
<Setter Property="local:ScrollHoverAreaBehaviour.ScrollRightRect" Value="{Binding ElementName=ScrollRightRect}"/>
<Setter TargetName="ScrollLeftRect" Property="Fill" Value="Red"/>
<Setter TargetName="ScrollRightRect" Property="Fill" Value="Red"/>
</Trigger>
I know this Trigger is being tripped as the rectangles fill Red as expected.
Can anyone spot what I'm doing wrong from these snippets?
Thanks in advance.
Rob
First, let's confirm you're not doing anything wrong, and the problem has nothing to do with the attached behaviors.
<Button>
<Button.Template>
<ControlTemplate TargetType="Button">
<Border Background="Yellow">
<StackPanel>
<TextBlock x:Name="theText" Text="Hello" />
<ContentPresenter />
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Content" Value="{Binding ElementName=theText, Path=Text}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
This snippet should cause "Hello" to appear twice when I mouse over the button, but it doesn't, and I get the same error as you:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=theText'. BindingExpression:Path=Text; DataItem=null; target element is 'Button' (Name=''); target property is 'Content' (type 'Object')
This is explainable - once the binding is set on the Button, it won't be able to find a control named 'theText', because the Button lives in a different NameScope.
An alternative
Some WPF controls need to do something similar to you - they assume that a specific control exists in the tree that they will interact with. But they don't use properties - they use names.
Start by giving the controls a name - the convention is to use "PART_" prefix:
<Rectangle ... Name="PART_ScrollLeftRect" />
Now put code like this in your callback when IsScrollHover is set:
private static void IsScrollHoverSetCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (Slider) d;
if ((bool)e.NewValue == false)
return;
target.ApplyTemplate();
var leftRectangle = target.Template.FindName("PART_ScrollLeftRect", target);
var rightRectangle = target.Template.FindName("PART_ScrollRightRect", target);
// Do things with the rectangles
}
Note that depending on when the IsScrollHost property is set, the template might not be ready yet. In that case, you might want to subscribe to the Loaded or similar event, and then call ApplyTemplate().
Although it might seem more complicated, it has one nice benefit: the markup will be simpler. A designer using Blend won't have to remember to wire up those complicated triggers, they just have to name the controls correctly.
The use of the PART_ prefix is a WPF convention, and normally used along with the TemplatePart attribute. An example of this is the TextBox. When you override the template of a TextBox, it won't function until you add a control named PART_ContentHost.
Update: I just blogged about template parts here: http://www.paulstovell.com/wpf-part-names

How to add new user control in TabControl.ContentTemplate?

I am little stuck with adding new instances of a usercontrol in a TabControl.ContentTemplate?
My Xaml is here:
<TabControl ItemsSource="{Binding Tables}">
<TabControl.ItemTemplate>
<DataTemplate>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type uc:mytest1}">
<uc:mytest1>
</uc:mytest1>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I am binding TabControl.ItemsSource property to an ObservableCollection and in the content template I am adding a user control, but when this app runs I am getting new items as TabItems but the content page is holding same user control, but I want new user controls to be added for each new TabItem.
I am very new to the WPF and may be I am doing a very basic mistake, kindly guide me.
The ControlTemplate determines the appearance of the elements of the tab control that are not part of the individual tab items. The ItemTemplate handles the content of the individual tab items. Additionally, a TabItem is a headered content control, which means it has two content type properties Content and Header with two separate templates ContentTemplate and HeaderTemplate. In order to be able to populate the tab items using binding, you need to style the TabItem using the above properties.
Example:
<Window x:Class="Example.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Window"
Title="Window2" Height="300" Width="300">
<Window.DataContext>
<Binding ElementName="Window" Path="VM"/>
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="TabItemHeaderTemplate">
<Grid>
<TextBlock Text="{Binding Header}"/>
<Ellipse Fill="Red" Width="40" Height="40" Margin="0,20,0,0"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="TabItemContentTemplate">
<Ellipse Fill="Green"/>
</DataTemplate>
<Style x:Key="TabItemContainerStyle" TargetType="TabItem">
<Setter Property="Header" Value="{Binding}"/>
<Setter Property="HeaderTemplate"
Value="{StaticResource TabItemHeaderTemplate}"/>
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate"
Value="{StaticResource TabItemContentTemplate}"/>
</Style>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{Binding Items}"
ItemContainerStyle="{StaticResource TabItemContainerStyle}"/>
</Grid>
</Window>
The code behind:
public partial class Window2 : Window
{
public TabControlVM VM { get; set; }
public Window2()
{
VM = new TabControlVM();
InitializeComponent();
}
}
And the view model classes:
public class TabControlVM
{
public ObservableCollection<TabItemVM> Items { get; set; }
public TabControlVM()
{
Items = new ObservableCollection<TabItemVM>();
Items.Add(new TabItemVM("tabitem1"));
Items.Add(new TabItemVM("tabitem2"));
Items.Add(new TabItemVM("tabitem3"));
Items.Add(new TabItemVM("tabitem4"));
}
}
public class TabItemVM
{
public string Header { get; set; }
public TabItemVM(string header)
{
Header = header;
}
}
Saurabh, When you set Template, usually DataTemplate, ControlTemplate etc, the visual elements inside these templates are reused in WPF with concept of UI Virtualization. TabControl typically displays only one item at a time, so it does not create new Visual Item for every tab item, instead it only changes that DataContext and refreshes bindings of "Selected Visual Item". Its loaded/unloaded events are fired, but the object is same always.
You can use loaded/unload events and write your code accordingly that your "Visual Element" which is your usercontrol, so that control should be stateless and is not dependent on old data. When new DataContext has applied you should refresh everything.
DataContextChanged, Loaded and Unloaded events can help you remove all dependencies on old data.
Otherwise, you an create a new TabItem manually with your UserControl as its Child and add it in TabControl instead of adding Data Items.
Adding TabItems manually will create new control for every item and in selected area different elements will appear based on selection.

Resources