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.
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'm writing a uwp program, where I want to bind a ObservableCollection to visibility of an Ellipse in a Ellipse matrix. Since I want to make the number of ellipses flexible, I initialize and bind them in the c# initialization code of the page. The problem is the ellipses' visibilities reflect the bool values which I set before the binding happens, but when the bool value changes after binding, the visibilities don't change with the bool value's change.
I declare the bool values in the App.xaml.cs
public int gameRow;`enter code here`
public int gameColumn;
public ObservableCollection<bool> gameMatrix;
In the page of ellipses, I declare a Grid to hold the ellipses in the GamePage.xaml.
<Grid Grid.Column="1" Grid.Row="0" x:Name="gameFlat"/>
In the corresponding GamePage.xaml.cs, I initialized ellipses, bind them to the bool values, and put them in a row/column in the gameFlat grid.
private void addEllipse(int i, int j, Binding[,] ellipseBindings)
{
ellipseBindings[i,j] = new Binding();
ellipseBindings[i, j].Source = gameMatrix[i*gameColumn+ j];
if (converter==null)
converter = new VisibilityConverter();
ellipseBindings[i, j].Converter = converter;
circles[i, j] = new Ellipse();
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
mySolidColorBrush.Color = Windows.UI.Color.FromArgb(255, 255, 106, 106);
circles[i, j].Fill = mySolidColorBrush;
circles[i, j].SetBinding(VisibilityProperty, ellipseBindings[i, j]);
Grid.SetRow(circles[i, j], i);
Grid.SetColumn(circles[i, j], j);
gameFlat.Children.Add(circles[i, j]);
}
The circles and the ellipseBinds are declared in the GamePage.xaml.cs as well.
Ellipse[,] circles;
Binding[,] EllipseBindings;
VisibilityConverter converter;
The visibility of ellipses are controlled by the bool values in the first time, but when I try to change the bool value after the binding, in a time tick function of a timer, the change of bool values don't affect the ellipses.
private void timer_tick(object sender, object e)
{
int testCount = pkgCountDown % (gameRow * gameColumn);
gameMatrix[(testCount % gameRow)* gameColumn
+ pkgCountDown% gameColumn ] = true;
gameCountDown--;
pkgCountDown--;
}
What's the wrong place in my code? Could you help me to make it work? Thank you!
-------update 2018/06/07--------
I tried to wrap the bool values in a class called isShownNotify which implements the INotifyPropertyChanged interface like below:
public class isShownNotify : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _isShown;
public bool isShown
{
get { return _isShown; }
set
{
if (_isShown != value)
{
isShown = value;
}
OnPropertyChanged();
}
}
public void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string name = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler!=null)
handler(this, new PropertyChangedEventArgs(name));
}
}
Then I changed the converter like below:
object IValueConverter.Convert(object value, Type targetType, object parameter, string language)
{
Type objectType = value.GetType();
PropertyInfo objectInfo = objectType.GetProperty("isShown");
bool boolValue = (bool)objectInfo.GetValue(value, null);
if (boolValue == true)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
But in the running of the program, the changes on bool values still don't change the visibility of my Ellipses.
When I set breakpoints in the OnPropertyChanged() function, I see that the value of PropertyChanged(the PropertyChangedEventHandler) is null. What's wrong with it? Do I bind it to the visibility of ellipses in a wrong way?
#MD Muzibur Rahman's suggestion was correct. You need to define a custom class and implement INotifyPropertyChangedinterface for it. I saw that you've done it. That's great. But you said that it doesn't work. After checking your code, the issue was due to that you want to change the property's value dynamically, but you have not bound to your property in your binding like this binding.Path = new PropertyPath("IsShown");. That's the reason why it doesn't work for you.
I've made a simple code sample for your reference:
<StackPanel>
<Grid Grid.Column="1" Grid.Row="0" x:Name="gameFlat">
</Grid>
<Button Content="add" Click="Button_Click"></Button>
<Button Content="Hide" Click="Button_Click_1"></Button>
</StackPanel>
public ObservableCollection<gameMatrix> gameMatrixlist;
Binding binding;
Ellipse ellipse;
private void addEllipse()
{
gameMatrixlist = new ObservableCollection<gameMatrix>() { new gameMatrix() {IsShown = true } };
binding = new Binding();
binding.Source = gameMatrixlist[0];
binding.Path = new PropertyPath("IsShown");
binding.Converter = new VisibilityConverter();
ellipse = new Ellipse();
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
mySolidColorBrush.Color = Windows.UI.Color.FromArgb(255, 255, 106, 106);
ellipse.Fill = mySolidColorBrush;
ellipse.Height = 100;
ellipse.Width = 100;
ellipse.SetBinding(VisibilityProperty, binding);
Grid.SetRow(ellipse, 0);
Grid.SetColumn(ellipse, 0);
gameFlat.Children.Add(ellipse);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
addEllipse();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
gameMatrixlist[0].IsShown = false;
}
public class gameMatrix : INotifyPropertyChanged
{
private bool _IsShown;
public bool IsShown
{
get { return _IsShown; }
set
{
if (value == _IsShown) return;
_IsShown = value;
OnPropertyChanged("IsShown");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class VisibilityConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value != null)
{
bool boolValue = (bool)value;
if (boolValue == true)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return Visibility.Collapsed;
}
}
I have a hexagon grid which I want to populate with some models inherited from labels (suggestions for a better class to use instead of labels are welcome). I want to bind a private variable (xPos for example) from my model to its grid properties.
I have these two models.
Unit
public class Unit : Label, INotifyPropertyChanged
{
public Unit() { } //Grass
public Unit(int x, int y)
{
this.xPos = x;
this.yPos = y;
this.mouseLeft = false;
this.mouseRight = false;
}
public static readonly RoutedEvent ClickEvent;
private int _xPos, _yPos;
private bool mouseLeft, mouseRight;
public int xPos
{
get { return _xPos; }
set
{
_xPos = value;
NotifyPropertyChanged("xPos");
}
}
public int yPos
{
get { return _yPos; }
set
{
_yPos = value;
NotifyPropertyChanged("yPos");
}
}
private string _type = "none";
public string type
{
get { return _type; }
set
{
_type = value;
}
}
static Unit()
{
ClickEvent = ButtonBase.ClickEvent.AddOwner(typeof(Unit));
}
public event RoutedEventHandler Click
{
add { AddHandler(ClickEvent, value); }
remove { RemoveHandler(ClickEvent, value); }
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
this.mouseLeft = e.LeftButton.ToString().Equals("Pressed");
this.mouseRight = e.RightButton.ToString().Equals("Pressed");
base.OnMouseDown(e);
CaptureMouse();
}
protected override void OnMouseUp(MouseButtonEventArgs e)
{
base.OnMouseUp(e);
if (this.mouseRight)
{
this.xPos = (int)this.GetValue(Grid.RowProperty);
this.yPos = (int)this.GetValue(Grid.ColumnProperty);
this.type = this.GetType().Name;
}
else if (this.mouseLeft)
{
MainWindow.objectInspector.selectedUnit = this;
}
else
{
MessageBox.Show("Kaos");
}
if (IsMouseCaptured)
{
ReleaseMouseCapture();
if (IsMouseOver)
RaiseEvent(new RoutedEventArgs(ClickEvent, this));
}
}
}
And this Model (inherited from Unit)
public class Soldier : Unit
{
public Soldier()
{
// get bitmapimage from resources and assign to img
Uri resourceUri = new Uri("Resources/Soldier.jpg", UriKind.Relative);
StreamResourceInfo streamInfo = Application.GetResourceStream(resourceUri);
BitmapFrame temp = BitmapFrame.Create(streamInfo.Stream);
var brush = new ImageBrush();
brush.ImageSource = temp;
this.Background = brush;
}
}
And this WPF
<local:Soldier xPos="10" yPos="5" Grid.Row="Binding Path=this.yPos" Grid.Column="Binding Path=this.xPos" />
<local:Soldier xPos="7" yPos="7" Grid.Row="Binding Path=this.yPos" Grid.Column="Binding Path=this.xPos" />
How can I bind the Soldiers Grid.Row to its yPos?
I can't understand why you've created such a misleading title for your question. You're not trying to bind to private variables... you're trying to bind to public properties like everyone else here. Having said that, have you tried using a Binding with RelativeSource?:
<local:Soldier xPos="10" yPos="5" Grid.Row="{Binding yPos, RelativeSource=
{RelativeSource Self}}" Grid.Column="{Binding xPos, RelativeSource=
{RelativeSource Self}}" />
<local:Soldier xPos="7" yPos="7" Grid.Row="{Binding yPos, RelativeSource=
{RelativeSource Self}}" Grid.Column="{Binding xPos, RelativeSource=
{RelativeSource Self}}" />
Has anyone had any luck applying the MahApps.Metro style to a NavigationWindow? I have implemented it for a Window just fine, but need to apply it to a NavigationWindow with Pages. I tried extending NavigationWindow and adding the modifications from MetroWindow like so, but no luck. The Window has a standard title bar and border, and the content is completely black.
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Navigation;
using MahApps.Metro.Native;
namespace MahApps.Metro.Controls
{
[TemplatePart(Name = PART_TitleBar, Type = typeof(UIElement))]
[TemplatePart(Name = PART_WindowCommands, Type = typeof(WindowCommands))]
public class MetroNavigationWindow : NavigationWindow
{
private const string PART_TitleBar = "PART_TitleBar";
private const string PART_WindowCommands = "PART_WindowCommands";
public static readonly DependencyProperty ShowIconOnTitleBarProperty = DependencyProperty.Register("ShowIconOnTitleBar", typeof(bool), typeof(MetroNavigationWindow), new PropertyMetadata(true));
public static readonly DependencyProperty ShowTitleBarProperty = DependencyProperty.Register("ShowTitleBar", typeof(bool), typeof(MetroNavigationWindow), new PropertyMetadata(true));
public static readonly DependencyProperty ShowMinButtonProperty = DependencyProperty.Register("ShowMinButton", typeof(bool), typeof(MetroNavigationWindow), new PropertyMetadata(true));
public static readonly DependencyProperty ShowCloseButtonProperty = DependencyProperty.Register("ShowCloseButton", typeof(bool), typeof(MetroNavigationWindow), new PropertyMetadata(true));
public static readonly DependencyProperty ShowMaxRestoreButtonProperty = DependencyProperty.Register("ShowMaxRestoreButton", typeof(bool), typeof(MetroNavigationWindow), new PropertyMetadata(true));
public static readonly DependencyProperty TitlebarHeightProperty = DependencyProperty.Register("TitlebarHeight", typeof(int), typeof(MetroNavigationWindow), new PropertyMetadata(30));
public static readonly DependencyProperty TitleCapsProperty = DependencyProperty.Register("TitleCaps", typeof(bool), typeof(MetroNavigationWindow), new PropertyMetadata(true));
public static readonly DependencyProperty SaveWindowPositionProperty = DependencyProperty.Register("SaveWindowPosition", typeof(bool), typeof(MetroNavigationWindow), new PropertyMetadata(false));
public static readonly DependencyProperty WindowPlacementSettingsProperty = DependencyProperty.Register("WindowPlacementSettings", typeof(IWindowPlacementSettings), typeof(MetroNavigationWindow), new PropertyMetadata(null));
public static readonly DependencyProperty TitleForegroundProperty = DependencyProperty.Register("TitleForeground", typeof(Brush), typeof(MetroNavigationWindow));
public static readonly DependencyProperty IgnoreTaskbarOnMaximizeProperty = DependencyProperty.Register("IgnoreTaskbarOnMaximize", typeof(bool), typeof(MetroNavigationWindow), new PropertyMetadata(false));
public static readonly DependencyProperty GlowBrushProperty = DependencyProperty.Register("GlowBrush", typeof(SolidColorBrush), typeof(MetroNavigationWindow), new PropertyMetadata(null));
public static readonly DependencyProperty FlyoutsProperty = DependencyProperty.Register("Flyouts", typeof(FlyoutsControl), typeof(MetroNavigationWindow), new PropertyMetadata(null));
public static readonly DependencyProperty WindowTransitionsEnabledProperty = DependencyProperty.Register("WindowTransitionsEnabled", typeof(bool), typeof(MetroNavigationWindow), new PropertyMetadata(true));
bool isDragging;
public bool WindowTransitionsEnabled
{
get { return (bool)this.GetValue(WindowTransitionsEnabledProperty); }
set { SetValue(WindowTransitionsEnabledProperty, value); }
}
public FlyoutsControl Flyouts
{
get { return (FlyoutsControl)GetValue(FlyoutsProperty); }
set { SetValue(FlyoutsProperty, value); }
}
public WindowCommands WindowCommands { get; set; }
public bool IgnoreTaskbarOnMaximize
{
get { return (bool)this.GetValue(IgnoreTaskbarOnMaximizeProperty); }
set { SetValue(IgnoreTaskbarOnMaximizeProperty, value); }
}
public Brush TitleForeground
{
get { return (Brush)GetValue(TitleForegroundProperty); }
set { SetValue(TitleForegroundProperty, value); }
}
public bool SaveWindowPosition
{
get { return (bool)GetValue(SaveWindowPositionProperty); }
set { SetValue(SaveWindowPositionProperty, value); }
}
public IWindowPlacementSettings WindowPlacementSettings
{
get { return (IWindowPlacementSettings)GetValue(WindowPlacementSettingsProperty); }
set { SetValue(WindowPlacementSettingsProperty, value); }
}
public bool ShowIconOnTitleBar
{
get { return (bool)GetValue(ShowIconOnTitleBarProperty); }
set { SetValue(ShowIconOnTitleBarProperty, value); }
}
public bool ShowTitleBar
{
get { return (bool)GetValue(ShowTitleBarProperty); }
set { SetValue(ShowTitleBarProperty, value); }
}
public bool ShowMinButton
{
get { return (bool)GetValue(ShowMinButtonProperty); }
set { SetValue(ShowMinButtonProperty, value); }
}
public bool ShowCloseButton
{
get { return (bool)GetValue(ShowCloseButtonProperty); }
set { SetValue(ShowCloseButtonProperty, value); }
}
public int TitlebarHeight
{
get { return (int)GetValue(TitlebarHeightProperty); }
set { SetValue(TitlebarHeightProperty, value); }
}
public bool ShowMaxRestoreButton
{
get { return (bool)GetValue(ShowMaxRestoreButtonProperty); }
set { SetValue(ShowMaxRestoreButtonProperty, value); }
}
public bool TitleCaps
{
get { return (bool)GetValue(TitleCapsProperty); }
set { SetValue(TitleCapsProperty, value); }
}
public SolidColorBrush GlowBrush
{
get { return (SolidColorBrush)GetValue(GlowBrushProperty); }
set { SetValue(GlowBrushProperty, value); }
}
public string WindowTitle
{
get { return TitleCaps ? Title.ToUpper() : Title; }
}
public MetroNavigationWindow()
{
Loaded += this.MetroWindow_Loaded;
}
private void MetroWindow_Loaded(object sender, RoutedEventArgs e)
{
VisualStateManager.GoToState(this, "AfterLoaded", true);
if (!ShowTitleBar)
{
//Disables the system menu for reasons other than clicking an invisible titlebar.
IntPtr handle = new WindowInteropHelper(this).Handle;
UnsafeNativeMethods.SetWindowLong(handle, UnsafeNativeMethods.GWL_STYLE, UnsafeNativeMethods.GetWindowLong(handle, UnsafeNativeMethods.GWL_STYLE) & ~UnsafeNativeMethods.WS_SYSMENU);
}
if (this.Flyouts == null)
{
this.Flyouts = new FlyoutsControl();
}
}
static MetroNavigationWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MetroNavigationWindow), new FrameworkPropertyMetadata(typeof(MetroNavigationWindow)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (WindowCommands == null)
WindowCommands = new WindowCommands();
var titleBar = GetTemplateChild(PART_TitleBar) as UIElement;
if (ShowTitleBar && titleBar != null)
{
titleBar.MouseDown += TitleBarMouseDown;
titleBar.MouseUp += TitleBarMouseUp;
titleBar.MouseMove += TitleBarMouseMove;
}
else
{
MouseDown += TitleBarMouseDown;
MouseUp += TitleBarMouseUp;
MouseMove += TitleBarMouseMove;
}
}
protected override void OnStateChanged(EventArgs e)
{
if (WindowCommands != null)
{
WindowCommands.RefreshMaximiseIconState();
}
base.OnStateChanged(e);
}
protected void TitleBarMouseDown(object sender, MouseButtonEventArgs e)
{
var mousePosition = e.GetPosition(this);
bool isIconClick = ShowIconOnTitleBar && mousePosition.X <= TitlebarHeight && mousePosition.Y <= TitlebarHeight;
if (e.ChangedButton == MouseButton.Left)
{
if (isIconClick)
{
if (e.ClickCount == 2)
{
Close();
}
else
{
ShowSystemMenuPhysicalCoordinates(this, PointToScreen(new Point(0, TitlebarHeight)));
}
}
else
{
IntPtr windowHandle = new WindowInteropHelper(this).Handle;
UnsafeNativeMethods.ReleaseCapture();
var wpfPoint = PointToScreen(Mouse.GetPosition(this));
short x = Convert.ToInt16(wpfPoint.X);
short y = Convert.ToInt16(wpfPoint.Y);
int lParam = x | (y << 16);
UnsafeNativeMethods.SendMessage(windowHandle, Constants.WM_NCLBUTTONDOWN, Constants.HT_CAPTION, lParam);
if (e.ClickCount == 2 && (ResizeMode == ResizeMode.CanResizeWithGrip || ResizeMode == ResizeMode.CanResize))
{
WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
}
}
}
else if (e.ChangedButton == MouseButton.Right)
{
ShowSystemMenuPhysicalCoordinates(this, PointToScreen(mousePosition));
}
}
protected void TitleBarMouseUp(object sender, MouseButtonEventArgs e)
{
isDragging = false;
}
private void TitleBarMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed)
{
isDragging = false;
}
if (isDragging
&& WindowState == WindowState.Maximized
&& ResizeMode != ResizeMode.NoResize)
{
// Calculating correct left coordinate for multi-screen system.
Point mouseAbsolute = PointToScreen(Mouse.GetPosition(this));
double width = RestoreBounds.Width;
double left = mouseAbsolute.X - width / 2;
// Check if the mouse is at the top of the screen if TitleBar is not visible
if (!ShowTitleBar && mouseAbsolute.Y > TitlebarHeight)
return;
// Aligning window's position to fit the screen.
double virtualScreenWidth = SystemParameters.VirtualScreenWidth;
left = left + width > virtualScreenWidth ? virtualScreenWidth - width : left;
var mousePosition = e.MouseDevice.GetPosition(this);
// When dragging the window down at the very top of the border,
// move the window a bit upwards to avoid showing the resize handle as soon as the mouse button is released
Top = mousePosition.Y < 5 ? -5 : mouseAbsolute.Y - mousePosition.Y;
Left = left;
// Restore window to normal state.
WindowState = WindowState.Normal;
}
}
internal T GetPart<T>(string name) where T : DependencyObject
{
return (T)GetTemplateChild(name);
}
private static void ShowSystemMenuPhysicalCoordinates(Window window, Point physicalScreenLocation)
{
if (window == null) return;
var hwnd = new WindowInteropHelper(window).Handle;
if (hwnd == IntPtr.Zero || !UnsafeNativeMethods.IsWindow(hwnd))
return;
var hmenu = UnsafeNativeMethods.GetSystemMenu(hwnd, false);
var cmd = UnsafeNativeMethods.TrackPopupMenuEx(hmenu, Constants.TPM_LEFTBUTTON | Constants.TPM_RETURNCMD, (int)physicalScreenLocation.X, (int)physicalScreenLocation.Y, hwnd, IntPtr.Zero);
if (0 != cmd)
UnsafeNativeMethods.PostMessage(hwnd, Constants.SYSCOMMAND, new IntPtr(cmd), IntPtr.Zero);
}
}
}
To accomplish this, I am using a MetroWindow as my main navigation window, and a Frame within that MetroWindow which handles the navigation.
Navigation Window
<Controls:MetroWindow x:Class="TestWpfApplicationMahApps.Metro.NavWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro" Title="NavWindow" Height="300" Width="300">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Colours.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/Blue.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/BaseLight.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Frame Source="Page1.xaml" NavigationUIVisibility="Hidden"></Frame>
</Controls:MetroWindow>
This allows me to not change any of the navigation logic, it can be called from the NavigationService just like it was when using a NavigationWindow.
Page 1 .cs
/// <summary>
/// Interaction logic for Page1.xaml
/// </summary>
public partial class Page1 : Page
{
public Page1()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new Page2());
}
}
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.