I needed a marquee for a project and after much Googling and trial and error, I created one. However, the animation itself is a little jittery. I need some pointers on how to improve the performance of this. Thanks.
p.s. Some of the code may be redundant...
public class Marquee : Canvas
{
static Marquee()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Marquee), new FrameworkPropertyMetadata(typeof(Marquee)));
}
private IList<string> _lines = new List<string>();
private IList<string> Lines
{
get
{
return _lines;
}
set
{
_lines = value;
}
}
public double FontSize
{
get { return (double)GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
// Using a DependencyProperty as the backing store for FontSize. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontSizeProperty =
DependencyProperty.Register("FontSize", typeof(double), typeof(Marquee));
public Brush FontBrush
{
get { return (Brush)GetValue(FontBrushProperty); }
set { SetValue(FontBrushProperty, value); }
}
// Using a DependencyProperty as the backing store for FontBrush. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontBrushProperty =
DependencyProperty.Register("FontBrush", typeof(Brush), typeof(Marquee));
public string SourceFile { get; set; }
List<RenderTargetBitmap> _images = new List<RenderTargetBitmap>();
private void CreateBitmaps()
{
foreach (var line in Lines)
{
FormattedText ft = new FormattedText(line,
System.Globalization.CultureInfo.CurrentUICulture,
System.Windows.FlowDirection.LeftToRight,
new Typeface(FontFamily.Source),
FontSize,
FontBrush);
if (ft.Height == 0 || ft.Width == 0)
continue;
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
drawingContext.DrawText(ft, new Point(0, 0));
drawingContext.Close();
RenderTargetBitmap bmp = new RenderTargetBitmap((int)ft.Width, (int)ft.Height, 72, 72, PixelFormats.Pbgra32);
bmp.Render(drawingVisual);
bmp.Freeze();
_images.Add(bmp);
}
}
private int nextImgIndex = 0;
private Image _Image;
private void GetNextImage()
{
if (_images.Count == 0)
return;
if (nextImgIndex >= _images.Count)
nextImgIndex = 0;
_Image.Source = _images.ElementAt(nextImgIndex++);
}
private string _curStr = null;
private string CurrentString
{
get
{
return _curStr;
}
set
{
_curStr = value;
}
}
TextBlock _textBlock = new TextBlock();
DispatcherTimer timer;
public Marquee()
{
Loaded += Marquee_Loaded;
FontSize = 12;
FontBrush = Brushes.Black;
if (Rate == 0d)
{
Rate = 150d;
}
this.CacheMode = new BitmapCache(2);
FontBrush.Freeze();
_Image = new Image();
_Image.CacheMode = new BitmapCache();
_Image.ClipToBounds = false;
FontFamily = new FontFamily("Calibri");
this.Children.Add(_Image);
}
void Marquee_Loaded(object sender, RoutedEventArgs e)
{
ReadFile();
CreateBitmaps();
CreateAnimation();
//throw new NotImplementedException();
}
//[ValueConversion(typeof(string), typeof(TimeSpan))]
//public class StringFormatConverter : IValueConverter
//{
// public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
// {
// string[] vals = ((string)value).Split(new[] { ':' });
// if (vals.Count() != 3)
// throw new FormatException(string.Format("Invalid timespan format : {0}", value));
// return new TimeSpan(int.Parse(vals[0]), int.Parse(vals[1]), int.Parse(vals[2]));
// }
// public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
// {
// throw new NotImplementedException();
// }
//}
DoubleAnimation anim;
TranslateTransform transform;
Duration duration;
public double Rate { get; set; }
void CreateAnimation()
{
if (CurrentString == null)
return;
GetNextImage();
transform = new TranslateTransform(Application.Current.MainWindow.ActualWidth, 0);
_Image.RenderTransform = transform;
var width = _Image.Source.Width;
double secs = (Application.Current.MainWindow.ActualWidth + width) / Rate;
duration = new Duration(TimeSpan.FromSeconds(secs));
anim = new DoubleAnimation(-width, duration);
anim.Completed += anim_Completed;
transform.BeginAnimation(TranslateTransform.XProperty, anim);
}
void anim_Completed(object sender, EventArgs e)
{
CreateAnimation();
}
double MeasureStringLength(string text)
{
if (text == null)
return 0;
FormattedText ft = new FormattedText(text,
System.Globalization.CultureInfo.CurrentUICulture,
System.Windows.FlowDirection.LeftToRight,
new Typeface(_textBlock.FontFamily.ToString()),
FontSize,
FontBrush);
return ft.Width;
}
public FontFamily FontFamily
{
get { return (FontFamily)GetValue(FontFamilyProperty); }
set { SetValue(FontFamilyProperty, value); }
}
// Using a DependencyProperty as the backing store for FontFamily. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontFamilyProperty =
DependencyProperty.Register("FontFamily", typeof(FontFamily), typeof(Marquee));
FormattedText GetFormattedText(string text)
{
if (text == null)
return null;
FormattedText ft = new FormattedText(text,
System.Globalization.CultureInfo.CurrentUICulture,
System.Windows.FlowDirection.LeftToRight,
new Typeface(_textBlock.FontFamily.ToString()),
FontSize,
FontBrush);
return ft;
}
void ReadFile()
{
if (SourceFile == null)
return;
StreamReader fR = new StreamReader(SourceFile);
string line;
while ((line = fR.ReadLine()) != null)
{
Lines.Add(line);
}
if (Lines.Count > 0)
CurrentString = Lines[0];
}
}
I'm modified your control in the following manner (removing the transform animation, using direct rendering instead of rendering to image cache):
public static readonly DependencyProperty OffsetProperty = DependencyProperty.Register("Offset", typeof(double),
typeof(Marquee), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));
//
public double Offset {
get { return (double)GetValue(OffsetProperty); }
}
protected override void OnRender(DrawingContext dc) {
dc.DrawText(currentText, new Point(Offset, 0)); // direct render
}
int nextTextIndex = 0;
FormattedText currentText;
void GetNextText() {
if(formattedTexts.Count == 0) return;
currentText = formattedTexts[(nextTextIndex++) % formattedTexts.Count];
}
void CreateAnimation() {
if(CurrentString == null)
return;
GetNextText();
double width = currentText.Width;
double secs = (Application.Current.MainWindow.ActualWidth + width) / Rate;
duration = new Duration(TimeSpan.FromSeconds(secs));
anim = new DoubleAnimation(0, -width, duration);
anim.Completed += anim_Completed;
BeginAnimation(OffsetProperty, anim);
}
//
void anim_Completed(object sender, EventArgs e) {
anim.Completed -= anim_Completed;
CreateAnimation();
}
List<FormattedText> formattedTexts = new List<FormattedText>();
void CreateTexts() {
foreach(var line in Lines) {
FormattedText ft = new FormattedText(line,
System.Globalization.CultureInfo.CurrentUICulture,
System.Windows.FlowDirection.LeftToRight,
new Typeface(FontFamily.Source),
FontSize,
FontBrush);
if(ft.Height == 0 || ft.Width == 0)
continue;
formattedTexts.Add(ft);
}
}
Now it is more smoother to me.
Related
I want to implement UWP connected animation in my WPF app. like this......
Is it possible? If it is then how can I do it?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Animation;
using Walterlv.Annotations;
namespace Walterlv.Demo.Media.Animation
{
public class ConnectedAnimation
{
internal ConnectedAnimation([NotNull] string key, [NotNull] UIElement source, [NotNull] EventHandler completed)
{
Key = key ?? throw new ArgumentNullException(nameof(key));
_source = source ?? throw new ArgumentNullException(nameof(source));
_reportCompleted = completed ?? throw new ArgumentNullException(nameof(completed));
}
public string Key { get; }
private readonly UIElement _source;
private readonly EventHandler _reportCompleted;
public bool TryStart([NotNull] UIElement destination)
{
return TryStart(destination, Enumerable.Empty<UIElement>());
}
public bool TryStart([NotNull] UIElement destination, [NotNull] IEnumerable<UIElement> coordinatedElements)
{
if (destination == null)
{
throw new ArgumentNullException(nameof(destination));
}
if (coordinatedElements == null)
{
throw new ArgumentNullException(nameof(coordinatedElements));
}
if (Equals(_source, destination))
{
return false;
}
// showing the animation?
// ready to connect the animation。
var adorner = ConnectedAnimationAdorner.FindFrom(destination);
var connectionHost = new ConnectedVisual(_source, destination);
adorner.Children.Add(connectionHost);
var storyboard = new Storyboard();
var animation = new DoubleAnimation(0.0, 1.0, new Duration(TimeSpan.FromSeconds(10.6)))
{
EasingFunction = new CubicEase {EasingMode = EasingMode.EaseInOut},
};
Storyboard.SetTarget(animation, connectionHost);
Storyboard.SetTargetProperty(animation, new PropertyPath(ConnectedVisual.ProgressProperty.Name));
storyboard.Children.Add(animation);
storyboard.Completed += (sender, args) =>
{
_reportCompleted(this, EventArgs.Empty);
//destination.ClearValue(UIElement.VisibilityProperty);
adorner.Children.Remove(connectionHost);
};
//destination.Visibility = Visibility.Hidden;
storyboard.Begin();
return true;
}
private class ConnectedVisual : DrawingVisual
{
public static readonly DependencyProperty ProgressProperty = DependencyProperty.Register(
"Progress", typeof(double), typeof(ConnectedVisual),
new PropertyMetadata(0.0, OnProgressChanged), ValidateProgress);
public double Progress
{
get => (double) GetValue(ProgressProperty);
set => SetValue(ProgressProperty, value);
}
private static bool ValidateProgress(object value) =>
value is double progress && progress >= 0 && progress <= 1;
private static void OnProgressChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ConnectedVisual) d).Render((double) e.NewValue);
}
public ConnectedVisual([NotNull] Visual source, [NotNull] Visual destination)
{
_source = source ?? throw new ArgumentNullException(nameof(source));
_destination = destination ?? throw new ArgumentNullException(nameof(destination));
_sourceBrush = new VisualBrush(source) {Stretch = Stretch.Fill};
_destinationBrush = new VisualBrush(destination) {Stretch = Stretch.Fill};
}
private readonly Visual _source;
private readonly Visual _destination;
private readonly Brush _sourceBrush;
private readonly Brush _destinationBrush;
private Rect _sourceBounds;
private Rect _destinationBounds;
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
if (VisualTreeHelper.GetParent(this) == null)
{
return;
}
var sourceBounds = VisualTreeHelper.GetContentBounds(_source);
if (sourceBounds.IsEmpty)
{
sourceBounds = VisualTreeHelper.GetDescendantBounds(_source);
}
_sourceBounds = new Rect(
_source.PointToScreen(sourceBounds.TopLeft),
_source.PointToScreen(sourceBounds.BottomRight));
_sourceBounds = new Rect(
PointFromScreen(_sourceBounds.TopLeft),
PointFromScreen(_sourceBounds.BottomRight));
var destinationBounds = VisualTreeHelper.GetContentBounds(_destination);
if (destinationBounds.IsEmpty)
{
destinationBounds = VisualTreeHelper.GetDescendantBounds(_destination);
}
_destinationBounds = new Rect(
_destination.PointToScreen(destinationBounds.TopLeft),
_destination.PointToScreen(destinationBounds.BottomRight));
_destinationBounds = new Rect(
PointFromScreen(_destinationBounds.TopLeft),
PointFromScreen(_destinationBounds.BottomRight));
}
private void Render(double progress)
{
var bounds = new Rect(
(_destinationBounds.Left - _sourceBounds.Left) * progress + _sourceBounds.Left,
(_destinationBounds.Top - _sourceBounds.Top) * progress + _sourceBounds.Top,
(_destinationBounds.Width - _sourceBounds.Width) * progress + _sourceBounds.Width,
(_destinationBounds.Height - _sourceBounds.Height) * progress + _sourceBounds.Height);
using (var dc = RenderOpen())
{
dc.DrawRectangle(_sourceBrush, null, bounds);
dc.PushOpacity(progress);
dc.DrawRectangle(_destinationBrush, null, bounds);
dc.Pop();
}
}
}
private class ConnectedAnimationAdorner : Adorner
{
private ConnectedAnimationAdorner([NotNull] UIElement adornedElement)
: base(adornedElement)
{
Children = new VisualCollection(this);
IsHitTestVisible = false;
}
internal VisualCollection Children { get; }
protected override int VisualChildrenCount => Children.Count;
protected override Visual GetVisualChild(int index) => Children[index];
protected override Size ArrangeOverride(Size finalSize)
{
foreach (var child in Children.OfType<UIElement>())
{
child.Arrange(new Rect(child.DesiredSize));
}
return finalSize;
}
internal static ConnectedAnimationAdorner FindFrom([NotNull] Visual visual)
{
if (Window.GetWindow(visual)?.Content is UIElement root)
{
var layer = AdornerLayer.GetAdornerLayer(root);
if (layer != null)
{
var adorner = layer.GetAdorners(root)?.OfType<ConnectedAnimationAdorner>().FirstOrDefault();
if (adorner == null)
{
adorner = new ConnectedAnimationAdorner(root);
layer.Add(adorner);
}
return adorner;
}
}
throw new InvalidOperationException("指定的 Visual 尚未连接到可见的视觉树中,找不到用于承载动画的容器。");
}
internal static void ClearFor([NotNull] Visual visual)
{
if (Window.GetWindow(visual)?.Content is UIElement root)
{
var layer = AdornerLayer.GetAdornerLayer(root);
var adorner = layer?.GetAdorners(root)?.OfType<ConnectedAnimationAdorner>().FirstOrDefault();
if (adorner != null)
{
layer.Remove(adorner);
}
}
}
}
}
}
How to use?
private int index;
private void AnimationButton_Click(object sender, RoutedEventArgs e)
{
BeginConnectedAnimation((UIElement)sender, ConnectionDestination);
}
private async void BeginConnectedAnimation(UIElement source, UIElement destination)
{
var service = ConnectedAnimationService.GetForCurrentView(this);
service.PrepareToAnimate($"Test{index}", source);
var animation = service.GetAnimation($"Test{index}");
animation?.TryStart(destination);
// use different Key when click。
index++;
}
See https://walterlv.com/post/connected-animation-of-wpf.html
I have here the circle image, what I want to do is to put some color in specific position.. For example, when I click the button1, the left side of the circle will be filled by red, and when I click the button2, the right side will be filled by as well, and when I click the button1 again, the color will be removed, and so on...
I've done some research about it, and found out 2 ways to do it. First is, to layover the circle with another image. Second is to draw, and use the Graphics class in C#..
Now, my question is, is there another possible way to do it? What is the best way?
P.S: The purpose of this is for the tooth chart. :)
Here's a Resizable, Clickable, UserControl based on qing`s post. You can click on the regions directly to toggle them, or change them via code.
public partial class ToothChart : UserControl
{
public ToothChart()
{
InitializeComponent();
this.DoubleBuffered = true;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (this.ParentForm != null)
{
this.ParentForm.FormClosing += (s, evt) => { OnHandleDestroyed(new EventArgs()); };
}
}
protected override void OnHandleDestroyed(EventArgs e)
{
base.OnHandleDestroyed(e);
if (this._pathTop != null)
{
this._pathTop.Dispose();
this._pathTop = null;
}
if (this._pathRight != null)
{
this._pathRight.Dispose();
this._pathRight = null;
}
if (this._pathBottom != null)
{
this._pathBottom.Dispose();
this._pathBottom = null;
}
if (this._pathLeft != null)
{
this._pathLeft.Dispose();
this._pathLeft = null;
}
if (this._pathCenter != null)
{
this._pathCenter.Dispose();
this._pathCenter = null;
}
}
private GraphicsPath _pathTop = null;
private GraphicsPath _pathLeft = null;
private GraphicsPath _pathBottom = null;
private GraphicsPath _pathRight = null;
private GraphicsPath _pathCenter = null;
private bool _TopRegion = false;
public bool TopRegion
{
get
{
return _TopRegion;
}
set
{
if (_TopRegion != value)
{
_TopRegion = value;
this.Invalidate();
}
}
}
private bool _RightRegion = false;
public bool RightRegion
{
get
{
return _RightRegion;
}
set
{
if (_RightRegion != value)
{
_RightRegion = value;
this.Invalidate();
}
}
}
private bool _BottomRegion = false;
public bool BottomRegion
{
get
{
return _BottomRegion;
}
set
{
if (_BottomRegion != value)
{
_BottomRegion = value;
this.Invalidate();
}
}
}
private bool _LeftRegion = false;
public bool LeftRegion
{
get
{
return _LeftRegion;
}
set
{
if (_LeftRegion != value)
{
_LeftRegion = value;
this.Invalidate();
}
}
}
private bool _CenterRegion = false;
public bool CenterRegion
{
get
{
return _CenterRegion;
}
set
{
if (_CenterRegion != value)
{
_CenterRegion = value;
this.Invalidate();
}
}
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
if (this.IsHandleCreated && this._pathTop != null)
{
this.UpdateRegions();
}
}
private void UpdateRegions()
{
int diameterBig = Math.Min(this.Width, this.Height) - 10;
int diameterSmall = Math.Min(this.Width, this.Height) / 3;
if (diameterBig > 0 && diameterSmall > 0)
{
Point _centerPoint = new Point(this.Width / 2, this.Height / 2);
Rectangle rectangle = new Rectangle(_centerPoint.X - diameterBig / 2, _centerPoint.Y - diameterBig / 2, diameterBig, diameterBig);
Rectangle rectangle2 = new Rectangle(_centerPoint.X - diameterSmall / 2, _centerPoint.Y - diameterSmall / 2, diameterSmall, diameterSmall);
_pathTop.Reset();
_pathTop.AddArc(rectangle, 225, 90);
_pathTop.AddArc(rectangle2, -45, -90);
_pathLeft.Reset();
_pathLeft.AddArc(rectangle, 135, 90);
_pathLeft.AddArc(rectangle2, -135, -90);
_pathBottom.Reset();
_pathBottom.AddArc(rectangle, 45, 90);
_pathBottom.AddArc(rectangle2, -225, -90);
_pathRight.Reset();
_pathRight.AddArc(rectangle, -45, 90);
_pathRight.AddArc(rectangle2, -315, -90);
_pathCenter.Reset();
_pathCenter.AddEllipse(rectangle2);
this.Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (this.IsHandleCreated)
{
if (this._pathTop == null)
{
this._pathTop = new GraphicsPath();
this._pathRight = new GraphicsPath();
this._pathBottom = new GraphicsPath();
this._pathLeft = new GraphicsPath();
this._pathCenter = new GraphicsPath();
this.UpdateRegions();
}
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
if (this.TopRegion)
{
e.Graphics.FillPath(Brushes.Blue, _pathTop);
}
e.Graphics.DrawPath(Pens.Black, _pathTop);
if (this.RightRegion)
{
e.Graphics.FillPath(Brushes.DarkRed, _pathRight);
}
e.Graphics.DrawPath(Pens.Black, _pathRight);
if (this.BottomRegion)
{
e.Graphics.FillPath(Brushes.Teal, _pathBottom);
}
e.Graphics.DrawPath(Pens.Black, _pathBottom);
if (this.LeftRegion)
{
e.Graphics.FillPath(Brushes.Yellow, _pathLeft);
}
e.Graphics.DrawPath(Pens.Black, _pathLeft);
if (this.CenterRegion)
{
e.Graphics.FillPath(Brushes.LightGreen, _pathCenter);
}
e.Graphics.DrawPath(Pens.Black, _pathCenter);
}
}
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
Point p = new Point(e.X, e.Y);
if (this._pathTop.IsVisible(p))
{
this.TopRegion = !this.TopRegion;
}
else if (this._pathRight.IsVisible(p))
{
this.RightRegion = !this.RightRegion;
}
else if (this._pathBottom.IsVisible(p))
{
this.BottomRegion = !this.BottomRegion;
}
else if (this._pathLeft.IsVisible(p))
{
this.LeftRegion = !this.LeftRegion;
}
else if (this._pathCenter.IsVisible(p))
{
this.CenterRegion = !this.CenterRegion;
}
}
}
I use TestStack White framework to automate testing of a WPF Application
It needs to open modal window and access TextBox in it. Everything works well, but White can't find Textbox, although it finds other elements of window
I tried the following lines of code:
TestStack.White.UIItems.TextBox TextBox = CreateBranch.Get<TestStack.White.UIItems.TextBox>(SearchCriteria.byAutomationId("Title"));
where CreateBranch is modal window
I also tried (SearchCriteria.All), (SearchCriteria.ByControlType) and nothing works
Coded UI tool finds this element well by AutomationID, but I need to do it in White
UISpy and other similar tools recognize this control and see its AutomationID
This textbox is custom control, here's code for it, I changed namespace name for privacy:
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace Test.Wpf.Controls.XTextBox
{
[TemplatePart(Name = "PART_Watermark", Type = typeof(TextBlock))]
[TemplatePart(Name = "PART_Pasword", Type = typeof(TextBlock))]
public class XTextBox : TextBox
{
#region Static
static XTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(XTextBox), new FrameworkPropertyMetadata(typeof(XTextBox)));
}
#endregion //Static
#region Fields
private TextBlock PART_Watermark;
private TextBlock PART_Pasword;
#endregion //Fields
#region DependencyProperties
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register(
"Watermark",
typeof(String),
typeof(XTextBox),
new PropertyMetadata(String.Empty));
public static readonly DependencyProperty WatermarkVerticalAlignmentProperty = DependencyProperty.Register(
"WatermarkVerticalAlignment",
typeof(VerticalAlignment),
typeof(XTextBox),
new PropertyMetadata(VerticalAlignment.Stretch));
public static readonly DependencyProperty WatermarkForegroundProperty = DependencyProperty.Register(
"WatermarkForeground",
typeof(Brush),
typeof(XTextBox),
new PropertyMetadata(new SolidColorBrush(Colors.Black)));
public static readonly DependencyProperty WatermarkFontSizeProperty = DependencyProperty.Register(
"WatermarkFontSize",
typeof(Double),
typeof(XTextBox),
new PropertyMetadata(12.0));
public static readonly DependencyProperty IsFloatingProperty = DependencyProperty.Register(
"IsFloating",
typeof(Boolean),
typeof(XTextBox),
new PropertyMetadata(false));
public static readonly DependencyProperty IsAccessNegativeProperty = DependencyProperty.Register(
"IsAccessNegative",
typeof(Boolean),
typeof(XTextBox),
new PropertyMetadata(true));
public static readonly DependencyProperty IsDigitOnlyProperty = DependencyProperty.Register(
"IsDigitOnly",
typeof(Boolean),
typeof(XTextBox),
new PropertyMetadata(false));
public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register(
"MaxValue",
typeof(Single),
typeof(XTextBox),
new PropertyMetadata(Single.NaN));
public static readonly DependencyProperty IsPasswordProperty = DependencyProperty.Register(
"IsPassword",
typeof(Boolean),
typeof(XTextBox),
new PropertyMetadata(false));
public static readonly DependencyProperty VisibilityMainTextProperty = DependencyProperty.Register(
"VisibilityMainText",
typeof(Visibility),
typeof(XTextBox),
new PropertyMetadata(Visibility.Visible));
#endregion //DependencyProperties
#region Properties
[Description("Gets or sets the watermark title")]
public String Watermark
{
get { return (String)GetValue(WatermarkProperty); }
set { SetValue(WatermarkProperty, value); }
}
[Description("Gets or sets the watermark vertical alignment")]
public VerticalAlignment WatermarkVerticalAlignment
{
get { return (VerticalAlignment)GetValue(WatermarkVerticalAlignmentProperty); }
set { SetValue(WatermarkVerticalAlignmentProperty, value); }
}
[Description("Gets or sets the watermark title color")]
public Brush WatermarkForeground
{
get { return (Brush)GetValue(WatermarkVerticalAlignmentProperty); }
set { SetValue(WatermarkVerticalAlignmentProperty, value); }
}
[Description("Gets or sets the watermark title font size")]
public Double WatermarkFontSize
{
get { return (Double)GetValue(WatermarkVerticalAlignmentProperty); }
set { SetValue(WatermarkVerticalAlignmentProperty, value); }
}
[Description("Gets or sets the textbox floating mode")]
public Boolean IsFloating
{
get { return (Boolean)GetValue(IsFloatingProperty); }
set { SetValue(IsFloatingProperty, value); }
}
[Description("Gets or sets the textbox access of negative values")]
public Boolean IsAccessNegative
{
get { return (Boolean)GetValue(IsAccessNegativeProperty); }
set { SetValue(IsAccessNegativeProperty, value); }
}
[Description("Gets or sets the textbox chars type")]
public Boolean IsDigitOnly
{
get { return (Boolean)GetValue(IsDigitOnlyProperty); }
set { SetValue(IsDigitOnlyProperty, value); }
}
[Description("Gets or sets the max input value (enable in digit mode only)")]
public Single MaxValue
{
get { return (Single)GetValue(MaxValueProperty); }
set { SetValue(MaxValueProperty, value); }
}
[Description("Gets or sets the textbox is passwordbox")]
public Boolean IsPassword
{
get { return (Boolean)GetValue(IsPasswordProperty); }
set { SetValue(IsPasswordProperty, value); }
}
public Visibility VisibilityMainText
{
get { return (Visibility)GetValue(VisibilityMainTextProperty); }
set { SetValue(VisibilityMainTextProperty, value); }
}
#endregion //Properties
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
PART_Watermark = GetTemplateChild("PART_Watermark") as TextBlock;
PART_Pasword = GetTemplateChild("PART_Pasword") as TextBlock;
SetWatermarkVisibility();
if (IsPassword)
{
VisibilityMainText = Visibility.Collapsed;
if (PART_Pasword != null)
{
PART_Pasword.Visibility = Visibility.Visible;
PART_Pasword.FontSize = 20;
}
}
else
{
VisibilityMainText = Visibility.Visible;
}
DataObject.AddPastingHandler(this, OnPaste);
}
protected void OnPaste(Object sender, DataObjectPastingEventArgs e)
{
try
{
var isText = e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true);
if (!isText) return;
var text = e.SourceDataObject.GetData(DataFormats.UnicodeText) as String;
if (!String.IsNullOrEmpty(text))
{
if (IsDigitOnly)
{
if (!IsAccessNegative)
{
var ch = text[0];
if (ch == 45)
{
e.CancelCommand();
}
}
for (int i = 0; i < text.Length; i++)
{
if (i == 0)
{
if (IsAccessNegative && text[i] == 45)
{
continue;
}
}
if (!Char.IsDigit(text[0]))
e.CancelCommand();
}
}
}
}
catch (Exception)
{
// ignored
e.Handled = true;
}
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
SetWatermarkVisibility();
if (IsPassword)
{
PART_Pasword.Text = new String('•', Text.Length);
}
}
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnLostKeyboardFocus(e);
SetWatermarkVisibility();
}
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnGotKeyboardFocus(e);
if (PART_Watermark != null)
{
PART_Watermark.Visibility = Visibility.Hidden;
}
}
protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
base.OnPreviewTextInput(e);
if (e.Text.Length == 0)
{
e.Handled = true;
return;
}
if (IsDigitOnly)
{
var ch = e.Text[0];
if (!Char.IsDigit(ch) && ch != 8 && ch != 46)
{
if (!(IsAccessNegative && ch == 45))
e.Handled = true;
}
if (IsFloating)
{
if (ch == 46 && Text.IndexOf('.') != -1)
{
e.Handled = true;
return;
}
}
if (!IsAccessNegative)
{
if (ch == 45)
{
e.Handled = true;
}
}
}
}
#region Private
private void SetWatermarkVisibility()
{
if (PART_Watermark != null)
{
PART_Watermark.Visibility = (Text != String.Empty || IsKeyboardFocused)? Visibility.Hidden : Visibility.Visible;
}
}
#endregion
}
}
Screenshot from UISpy
Let me know if that works
TextBox = (TextBox)CreateBranch
.Get(SearchCriteria.ByAutomationId("Title").AndOfFramework(WindowsFramework.Wpf));
Edited after new source added
You have to create a specific AutomationPeer for your custom control and return it via the override of the method OnCreateAutomationPeer().
Your control is a subclass of the TextBox control so you can just return a new TextBoxAutomationPeer instance or create your custom AutomationPeer from it.
public class XTextBox : TextBox
{
...
protected override AutomationPeer OnCreateAutomationPeer()
{
return new XTextBoxAutomationPeer(this);
// or just use the TextBoxAutomationPeer
// return new TextBoxAutomationPeer(this);
}
...
}
The custom automation peer
public class XTextBoxAutomationPeer : TextBoxAutomationPeer
{
public XTextBoxAutomationPeer(XTextBox owner)
: base(owner)
{
}
protected override string GetClassNameCore()
{
return "XTextBox";
}
}
[SetUpFixture]
public class SETUP_THAT_WILL_GET_CALL_LATER
{
[OneTimeSetUp]
public void OneTimeSetUp()
{
var applicationDirectory = TestContext.CurrentContext.TestDirectory;
var applicationPath = Path.Combine(applicationDirectory, #"..\..\..\, "your debug folder path here", "your application.exe here");
Application = Application.Launch(applicationPath);
Thread.Sleep(2000);
Window = Application.GetWindow("Title of your application", InitializeOption.WithCache);
}
[OneTimeTearDown()]
public void OneTimeTearDown()
{
Window.Dispose();
Application.Dispose();
}
public static Application Application;
public static Window Window;
}
Then in your test
[Test]
public void yourtest()
{
var textBox = SETUP_THAT_WILL_GET_CALL_LATER.**Window.Get<TextBox>("Your textbox name here");**
}
I want to make an application in which the user could add rectangle with customizable text inside it. The rectangle also can have another rectangles inside. Just as you can see on these picture:
I read about DrawingVisual, Shapes etc. So far I did it using DrawingVisual + Host, which derivies from FrameworkElement. DrawingVisual has FormattedText field, and list of Children elements; Host maintain drawing all elements.
The main problem is that, everytime user changes text in any child element I need to calculate new coordinates, width, height of all child elements. Maybe there is any method to do that automatically?
Also, DrawingVisual doesn't have any mouse events. So how to make all elements selectable / hoverable? Or should I derive from some other class?
Later I will post some code...
EDIT:
public class VisualHost: FrameworkElement
{
private VisualCollection _children;
private List<MyElement> _list;
public VisualHost(List<MyElement> list)
{
_children = new VisualCollection(this);
_list = list;
}
protected override int VisualChildrenCount
{
get { return _children.Count; }
}
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= _children.Count)
{
throw new ArgumentOutOfRangeException();
}
return _children[index];
}
private void CheckSize(MyElement element)
{
double sw = 0;
double mh = 0;
if (element.GetChildCount() > 0)
{
for (int i = 0; i < element.GetChildCount(); i++)
{
CheckSize(element.GetChild(i));
sw += element.GetChild(i).Width;
mh = Math.Max(mh, element.GetChild(i).Height);
}
}
element.Width = Math.Max(element.Formatted.Width, sw);
element.Height = element.Formatted.Height + mh;
}
private void DrawElement(double top, double left, MyElement element)
{
CheckSize(element);
var context = element.RenderOpen();
context.DrawRectangle( null, new Pen(Brushes.Black, 2d), new Rect(new Point(left, top), new Size(element.Width, element.Height)));
context.DrawText(element.Formatted, new Point(left, top));
top += element.Formatted.Height;
if (element.GetChildCount() > 0)
{
for (int i = 0; i < element.GetChildCount(); i++)
{
context.DrawRectangle(null, new Pen(Brushes.Black, 2d), new Rect(new Point(left, top), new Size(element.GetChild(i).Width, element.GetChild(i).Height)));
context.DrawText(element.GetChild(i).Formatted, new Point(left, top));
left += element.GetChild(i).Width;
}
}
context.Close();
_children.Add(element);
}
public void Redraw()
{
if (_list != null)
{
double top = 0, left = 0;
foreach (MyElement element in _list)
{
DrawElement(top, left, element);
top += element.Height + 10d;
}
}
}
}
public class MyElement: DrawingVisual
{
private string _text;
public string Text
{
get { return _text; }
set {
if (_text != value)
{
Typeface typeface = new Typeface(new FontFamily("Arial"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
Formatted = new FormattedText(value, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 12, Brushes.Red);
_text = value;
}
}
}
public FormattedText Formatted { get; private set; }
public double Height { get; set; }
public double Width { get; set; }
private List<MyElement> _children;
public MyElement GetChild(int i)
{
if (i < 0 || i >= _children.Count)
{
throw new ArgumentOutOfRangeException();
}
return _children[i];
}
public int GetChildCount()
{
return _children.Count;
}
public void AddChild(MyElement child)
{
_children.Add(child);
}
public MyElement(string Text)
{
this.Text = Text;
this._children = new List<MyElement>();
}
}
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
_list = new List<MyElement>();
_list.Add(new MyElement("text"));
var e = new MyElement("text 2");
e.AddChild(new MyElement("a"));
e.AddChild(new MyElement("b"));
e.AddChild(new MyElement("c"));
_list.Add(e);
_host = new VisualHost(_list);
MyCanvas.Children.Add(_host);
_host.Redraw();
}
This is my code for now. I wrote it only to check if idea is correct.
well I'm not sure if you would like this approach but you can actually do it very simple... I'm thinking maybe you can use blend to create a user control and design a label and a listbox in a stackpanel and set 'em all on autosizing.
or design 2 stack panels set 1 to do vertical orientation and the other one to do horizontal and add textblocks or something to the horizontal one.
Hy guys, I need your help again.
My XAML:
I would like to bind the Color of Line1 to my TestClassItem in XAML.
<Canvas Name="ElementCanvas">
<local:TestClass x:Name="TestClassItem" Width="50" Height="20" Position="20,100" LineColor="{Binding Stroke, ElementName=Line1}" ></local:TestClass>
<Line x:Name="Line1" X1="100" X2="300" Y1="50" Y2="50" Stroke="Red"/>
<Line x:Name="Line2" X1="100" X2="300" Y1="100" Y2="100" Stroke="{Binding Stroke, ElementName=Line1}" />
</Canvas>
My Created Class is pretty simple:
public class TestClass : Canvas
{
Line myLine = new Line();
public Color LineColor
{
get
{
return (Color)GetValue(LineStrokeProperty);
}
set
{
SetValue(LineStrokeProperty, value);
OnPropertyChanged("LineColor");
}
}
double X
{
get
{
return (double)Canvas.GetLeft(this);
}
set
{
Canvas.SetLeft(this, value);
OnPropertyChanged("X");
}
}
double Y
{
get
{
return (double)Canvas.GetTop(this) + Height / 2;
}
set
{
Canvas.SetTop(this, value - Height / 2);
OnPropertyChanged("Y");
}
}
public Point Position
{
get
{
return new Point(X, Y);
}
set
{
X = value.X;
Y = value.Y;
}
}
public static readonly DependencyProperty LineStrokeProperty = DependencyProperty.Register("LineColor", typeof(Color), typeof(TestClass), new FrameworkPropertyMetadata(Colors.Purple, FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(OnLineStrokePropertyChanged), new CoerceValueCallback(OnLineStrokePropertyCoerceValue)), new ValidateValueCallback(OnLineStrokePropertyValidateValue));
private static bool OnLineStrokePropertyValidateValue(object value)
{
return true;
}
private static object OnLineStrokePropertyCoerceValue(DependencyObject d, object baseValue)
{
return (Color)baseValue;
}
private static void OnLineStrokePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TestClass)d).SetValue(LineStrokeProperty, (Color)e.NewValue);
}
public TestClass()
{
DataContext = this;
Background = Brushes.Yellow;
SnapsToDevicePixels = true;
Binding b_width = new Binding("Width");
b_width.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
myLine.SetBinding(Line.X2Property, b_width);
Binding b_y1 = new Binding("Height");
b_y1.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
b_y1.Converter = new DoubleTwicer(); //Divides Height by 2
myLine.SetBinding(Line.Y1Property, b_y1);
Binding b_y2 = new Binding("Height");
b_y2.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
b_y2.Converter = new DoubleTwicer();
myLine.SetBinding(Line.Y2Property, b_y2);
Binding b_stroke = new Binding("LineColor");
b_stroke.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
b_stroke.Converter = new RaColorToSolidBrush();
myLine.SetBinding(Line.StrokeProperty, b_stroke);
this.Children.Add(myLine);
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Why doesn't it work?
Does anybody know how to Debug (see Values of bindings in Visual Studio?)
Thank you sou much!!!!
First of all! Thank you guys you already helped me but it works but not completely (DataContext = this; might be the problem so leave it:
public Brush LineColor
{
get
{
return (Brush)GetValue(LineStrokeProperty);
}
set
{
SetValue(LineStrokeProperty, value);
OnPropertyChanged("LineColor");
}
}
public static readonly DependencyProperty LineStrokeProperty = DependencyProperty.Register("LineColor", typeof(Brush), typeof(RaClickableLine), new FrameworkPropertyMetadata(Brushes.White, FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(OnLineStrokePropertyChanged), new CoerceValueCallback(OnLineStrokePropertyCoerceValue)), new ValidateValueCallback(OnLineStrokePropertyValidateValue));
private static bool OnLineStrokePropertyValidateValue(object value)
{
return true;
}
private static object OnLineStrokePropertyCoerceValue(DependencyObject d, object baseValue)
{
return (Brush)baseValue;
}
private static void OnLineStrokePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((RaClickableLine)d).SetValue(LineStrokeProperty, (Brush)e.NewValue);
}
public RaClickableLine()
{
SetValue(FrameworkElement.NameProperty, "ClickableLine");
Background = Brushes.AliceBlue;
SnapsToDevicePixels = true;
myLine.Stroke = Brushes.Blue;
myLine.X1 = 0;
myLine.X2 = ActualWidth;
myLine.Y1 = 10;
myLine.Y2 = 10;
Binding b_width = new Binding();
b_width.ElementName = "ClickableLine";
b_width.Path = new PropertyPath("Width");
b_width.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
myLine.SetBinding(Line.X2Property, b_width);
Children.Add(myLine);
}
It even don't show me the line.
If I leave b_width.ElementName = "ClickableLine"; and add DataContext = this; the line appears.