I have read various solutions to the problem and I now wonder which one is to be preferred in which situation (and mine especially)
I have created a Custom Control that Renders a Color wheel and a circle (Ellipse) on a canvas in the middle. I now want to be able to click and drag that circle as a selector on the color wheel.
Possible solutions include: Overriding the OnClick and / or OnMouseMove events and update the circles position by either dependency properties or using TemplateParts or even generating the circle in the Controls Code Behind.
I wonder if it would also be possible to use triggers in XAML to achieve this effect and which solution would deliver the "smoothest" motion.
Update 1: To address comments, here is some code:
ColorPicker.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TrigDebugUtil.Controls">
<Style x:Key="ColorPicker" TargetType="{x:Type local:ColorPicker}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ColorPicker}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Image Source="{TemplateBinding ColorWheelImage}" Width="500" Height="500"/>
<Canvas Width="10" Height="10" HorizontalAlignment="Center" VerticalAlignment="Center">
<Ellipse Fill="{TemplateBinding Property=SelectedColor}" Width="10" Height="10" Stroke="Black" StrokeThickness=".5" />
</Canvas>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
ColorPicker.cs
namespace TrigDebugUtil.Controls
{
public class ColorPicker : Control
{
#region Private Fields
#endregion //Private Fields
#region Dependency Properties
public static readonly DependencyProperty ColorWheelImageProperty = DependencyProperty.Register("ColorWheelImage", typeof(WriteableBitmap), typeof(ColorPicker));
public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register("SelectedColor", typeof(SolidColorBrush), typeof(ColorPicker));
#endregion //Dependency Properties
#region Properties
public WriteableBitmap ColorWheelImage
{
get { return (WriteableBitmap)GetValue(ColorWheelImageProperty); }
private set { SetValue(ColorWheelImageProperty, value); }
}
public SolidColorBrush SelectedColor
{
get { return (SolidColorBrush)GetValue(SelectedColorProperty); }
private set { SetValue(SelectedColorProperty, value); }
}
#endregion //Properties
static ColorPicker()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorPicker), new FrameworkPropertyMetadata(typeof(ColorPicker)));
}
public ColorPicker()
{
ColorWheelImage = new WriteableBitmap(500, 500, 96, 96, PixelFormats.Rgb24, null);
SelectedColor = Brushes.White;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Byte[] pixels = new Byte[1500 * 500];
// Update algo here
ColorWheelImage.WritePixels(new Int32Rect(0, 0, 500, 500), pixels, 1500, 0);
}
}
}
I want to be able to click on the ellipse in the canvas and move it to another location in the control itself (i.e. on the image)
For people having the same issue, one possible solution is to implement the draggable object as a Thumb and overriding its DragStarted DragDelta and DragCompleted events.
Here's an example:
private void OnThumbDragDelta(object sender, DragDeltaEventArgs e)
{
Thumb thumb = sender as Thumb;
double newX = Canvas.GetLeft(thumb) + e.HorizontalChange;
double newY = Canvas.GetTop(thumb) + e.VerticalChange;
Canvas.SetLeft(thumb, newX);
Canvas.SetTop(thumb, newY);
// This is optional for routed events.
e.RoutedEvent = ThumbDragDeltaEvent;
RaiseEvent(e);
}
without using the DragStarted and DragCompleted Events.
I'd like to see comments on wether this is a preferred solution or if it has any disadvantages. From what I can see it's not the most efficient, on fast mouse movements, the thumb movement is delayed and not always "right under the mouse"
Related
I'm using a rectangle drawn on an adorner to mark a region of interest on an image. The issue is that if I resize the window, the rectangle doesn't change size.
I'm new to WPF, so I've done a bunch of research, googling what I can with multiple different search terms. I actually just learned adorners that way, and I've gotten this far on that, but I've hit a wall on how to finish this last piece. I know that my problem is based in the size of the rectangle, but I don't know what to capture/look for to adjust it, since wpf resizes the actual image object on window resize, so there's no scale factor to look at.
Here's the XAML for the application I'm testing things in.
<Window x:Class="TestingAdorners.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestingAdorners"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid ClipToBounds="True">
<AdornerDecorator>
<Image Name="Btn" Source="nyan.png" Stretch="Uniform"/>
</AdornerDecorator>
</Grid>
</Window>
The adorner class:
class RoiAdorner : Adorner
{
public Rect rectangle = new Rect();
public RoiAdorner(UIElement adornedElement) : base(adornedElement)
{
rectangle.Height = 30;
rectangle.Width = 100;
IsHitTestVisible = false;
}
protected override void OnRender(DrawingContext drawingContext)
{
Pen pen = new Pen(Brushes.Green, 5);
drawingContext.DrawRectangle(null, pen, rectangle);
}
}
And the Xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
AdornerLayer.GetAdornerLayer(Btn).Add(new RoiAdorner(Btn));
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
}
The desired result is that the rectangle scales with the image object so that it always covers the same region of the image. The problem is I don't know how to capture a scale factor to scale it up and down as the window resizes.
Update: After thinking through Frenchy's suggestion I realized the answer is simply: "Normalize your coordinates"
you just adapt your render method like this:
class RoiAdorner : Adorner
{
public double factorX = 0d;
public double factorY = 0d;
public Rect rectangle = new Rect();
public RoiAdorner(UIElement adornedElement) : base(adornedElement)
{
rectangle.Height = 30;
rectangle.Width = 100;
IsHitTestVisible = false;
}
protected override void OnRender(DrawingContext drawingContext)
{
if (factorY == 0)
factorY = rectangle.Height / AdornedElement.DesiredSize.Height;
if (factorX == 0)
factorX = rectangle.Width / AdornedElement.DesiredSize.Width;
var r = new Rect(new Size(AdornedElement.DesiredSize.Width * factorX, AdornedElement.DesiredSize.Height * factorY));
//Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);
drawingContext.DrawRectangle(null, new Pen(Brushes.Red, 5), r);
}
this.AdornedElement.DesiredSize gives you the size of image.
The approach I would use is to render the picture and rectangle to the same thing. Then that one thing is stretched, scaled or whatever.
One way to do this would be to use a DrawingImage. Drawing methods are extremely efficient if rather low level.
<Grid ClipToBounds="True">
<AdornerDecorator>
<Image Name="img" Stretch="Uniform">
<Image.Source>
<DrawingImage PresentationOptions:Freeze="True">
<DrawingImage.Drawing>
<DrawingGroup>
<ImageDrawing Rect="0,0,595,446" ImageSource="DSC00025.jpg"/>
<GeometryDrawing Brush="Green">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,100,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</AdornerDecorator>
</Grid>
Another is with a visualbrush. Controls inherit from visual - this is somewhat higher level coding.
<Grid ClipToBounds="True">
<AdornerDecorator>
<Rectangle Name="rec">
<Rectangle.Fill>
<VisualBrush Stretch="Uniform">
<VisualBrush.Visual>
<Grid Height="446" Width="595">
<Image Source="DSC00025.jpg" Stretch="Fill"/>
<Rectangle Height="30" Width="100" Fill="Green"
VerticalAlignment="Top" HorizontalAlignment="Left"/>
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
</AdornerDecorator>
</Grid>
Note that both of these are quick and dirty illustrations to give you the idea. The image I picked at random off my hard drive is sized 446 * 595. You could calculate sizes or bind or stretch as suits your requirement best.
I have a WPF Application where I need to draw a large slider. The problem is drawing that large slider has a severe performance issue. What I did was reduce the size of the slider and redraw it when is needed. That is when Scrollviewer is scrolled and the slider is out of range, I place the slider in the Correct position and set it's min and max value.
It works fine until I want to reach the end near my thumb, it is rendered in a different place and interacts from a different one, given that there is very large area to scroll.
As you can see in the picture I need to place mouse in the red zone to drag the thumb but the thumb is drawn in a different position.
If anyone of you have a solution to this problem and know the reason, it would be a great help.
Sample Code is given below
Thanks in Advance.
Move the Scroll to the end and try dragging the thumb to reproduce the
problem.
MainWindow.xaml
<Window x:Class="SLiderTestApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350"
Loaded="MainWindow_OnLoaded">
<ScrollViewer x:Name="scrollViewer"
HorizontalScrollBarVisibility="Visible"
ScrollChanged="ScrollViewer_OnScrollChanged">
<Canvas x:Name="canvas" Width="600000000">
<Grid x:Name="_grid" Width="10000">
<Slider x:Name="slider" />
</Grid>
</Canvas>
</ScrollViewer>
</Window>
MainWindow.cs
namespace SLiderTestApplication
{
#region
using System.Windows;
using System.Windows.Controls;
#endregion
public partial class MainWindow : Window
{
#region Constructors and Destructors
public MainWindow()
{
this.InitializeComponent();
}
#endregion
#region Methods
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
this.slider.Maximum = 10000;
this.slider.Value = this.slider.Maximum;
}
private void ScrollViewer_OnScrollChanged(object sender, ScrollChangedEventArgs e)
{
var left = this.scrollViewer.HorizontalOffset - this.slider.ActualWidth + 200;
if (left < 0)
{
left = 0;
}
this._grid.Margin = new Thickness(left, 0, 0, 0);
}
#endregion
}
}
I try to rotate a Border and have the MainWindow change his size based on the new space taken by the Border rotation.
I've set SizeToContent="WidthAndHeight" but window size does't take change when I rotated the border.
Do I need to programmatically set the Width and Height for the MainWindow or this can be achieved changing the xaml code in some other way?
My xaml code:
<Window x:Class="MyClass.MainWindow"
WindowStyle="None" AllowsTransparency='True'
Topmost='False' Background="Transparent" ShowInTaskbar='False'
SizeToContent="WidthAndHeight" WindowStartupLocation="Manual">
<Border Name="MyBorder"
BorderBrush="Transparent"
Background="Transparent"
HorizontalAlignment="Left"
VerticalAlignment="Top"
RenderTransformOrigin="0.5,0.5">
</Border>
</Windows>
My c# code on Window_KeyDown:
# RotateTransform rt = new RotateTransform() is declared at class level.
if (e.Key == Key.I)
{
if (rt.Angle + 1 < 360)
{
rt.Angle += 1;
}
else
{
rt.Angle = 0;
}
MyBorder.RenderTransform = rt;
}
Use LayoutTransform instead of RenderTransform
From MSDN: Transforms Overview
LayoutTransform – A transform that is applied before the layout pass. After the transform is applied, the layout system processes the
transformed size and position of the element.
RenderTransform – A transform that modifies the appearance of the element but is applied after the layout pass is complete. By using the
RenderTransform property instead of the LayoutTransform property, you
can obtain performance benefits.
Example
<Border Name="MyBorder"
BorderBrush="Transparent"
Background="Transparent"
HorizontalAlignment="Left"
VerticalAlignment="Top"
RenderTransformOrigin="0.5,0.5">
<Border.LayoutTransform>
<RotateTransform Angle="90"/>
</Border.LayoutTransform>
</Border>
So in your case
RotateTransform rt = new RotateTransform(0.0, 0.5, 0.5);
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.I)
{
if (rt.Angle + 1 < 360)
{
rt.Angle += 1;
}
else
{
rt.Angle = 0;
}
MyBorder.LayoutTransform = rt;
}
}}
I'm working on my first WPF application and I'm testing out a custom control that is essential a circle with a play button drawn into the middle of it. I seem to have hit a bit of a hitch though. When I draw my play button I can't seem to get it to resize alongside the circle. Specifically, when I resize the circle to be wider or taller, the play button polygon remains the same size and in the same absolute position. Any pointer on setting up my XAML or code to correct this?
Existing XAML:
<Window x:Class="WPFTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:WPFTest">
<StackPanel>
<StackPanel.Resources>
<Style TargetType="my:GradientButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type my:GradientButton}">
<Grid>
<Ellipse Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Stroke="{TemplateBinding Foreground}" VerticalAlignment="Top" HorizontalAlignment="Left">
<Ellipse.Fill>
<LinearGradientBrush>
<GradientStop Color="{TemplateBinding GradientStart}" Offset="0"></GradientStop>
<GradientStop Color="{TemplateBinding GradientEnd}" Offset="1"></GradientStop>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Polygon Points="{TemplateBinding PlayPoints}" Fill="{TemplateBinding Foreground}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
<my:GradientButton Content="Button" Height="50" x:Name="gradientButton1" Width="50" GradientStart="#FFCCCCCC" GradientEnd="#FFAAAAAA" PlayPoints="18,12 35,25 18,38" />
</StackPanel>
</Window>
Code:
public class GradientButton : Button
{
internal static DependencyProperty GradientStartProperty;
internal static DependencyProperty GradientEndProperty;
internal static DependencyProperty PlayPointsProperty;
static GradientButton()
{
GradientStartProperty = DependencyProperty.Register("GradientStart", typeof(Color), typeof(GradientButton));
GradientEndProperty = DependencyProperty.Register("GradientEnd", typeof(Color), typeof(GradientButton));
PlayPointsProperty = DependencyProperty.Register("PlayPoints", typeof(PointCollection), typeof(GradientButton));
}
public Color GradientStart
{
get { return (Color)base.GetValue(GradientStartProperty); }
set { SetValue(GradientStartProperty, value); }
}
public Color GradientEnd
{
get { return (Color)base.GetValue(GradientEndProperty); }
set { SetValue(GradientEndProperty, value); }
}
public PointCollection PlayPoints
{
get
{
//this is where I'm trying to make the resizing dynamic, but this property never seems to get hit when I put in a breakpoint?
PointCollection result = new PointCollection();
double top = this.Width / 2.778;
double left = this.Width / 4.167;
double middle = this.Height / 2.00;
double right = this.Width / 1.429;
double bottom = this.Height / 1.316;
result.Add(new Point(left, top));
result.Add(new Point(right, middle));
result.Add(new Point(left, bottom));
return result;
}
set { SetValue(PlayPointsProperty, value); }
}
}
You need to set the Stretch property of the Polygon to Uniform
I am new to wpf and am having a problem which may or may not be trivial. I have defined a custom control as follows in the resource dictionary:
<ResourceDictionary
x:Class="SyringeSlider.Themes.Generic"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SyringeSlider">
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Canvas Height="{TemplateBinding Height}" Width="{TemplateBinding Width}" Name="syringeCanvas">
</Canvas>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Unfortunately I cannot go beyond this because I would like to draw a geometry onto the canvas consisting of a set of multiple line geometries whose dimensions are calculated as a function of the space available in the canvas. I believe that I need a code behind method to do this, but have not been able to determine how to link the xaml definition to a code behind method.
Note that I have set up a class x:Class="SyringeSlider.Themes.Generic" for specifically this purpose, but can't figure out which Canvas property to link the drawing method to.
My drawing method looks like this
private void CalculateSyringe()
{
int adjHeight = (int) Height - 1;
int adjWidth = (int) Width - 1;
// Calculate some very useful values based on the chart above.
int borderOffset = (int)Math.Floor(m_borderWidth / 2.0f);
int flangeLength = (int)(adjHeight * .05f);
int barrelLeftCol = (int)(adjWidth * .10f);
int barrelLength = (int)(adjHeight * .80);
int barrelRightCol = adjWidth - barrelLeftCol;
int coneLength = (int)(adjHeight * .10);
int tipLeftCol = (int)(adjWidth * .45);
int tipRightCol = adjWidth - tipLeftCol;
int tipBotCol = adjWidth - borderOffset;
Path mySyringePath = new Path();
PathGeometry mySyringeGeometry = new PathGeometry();
PathFigure mySyringeFigure = new PathFigure();
mySyringeFigure.StartPoint = new Point(0, 0);
Point pointA = new Point(0, flangeLength);
mySyringeFigure.Segments.Add(new LineSegment(pointA, true));
Point pointB = new Point();
pointB.Y = pointA.Y + barrelLength;
pointB.X = 0;
mySyringeFigure.Segments.Add(new LineSegment(pointB, true));
// You get the idea....Add more points in this way
mySyringeGeometry.Figures.Add(mySyringeFigure);
mySyringePath.Data = mySyringeGeometry;
}
SO my question is:
1) Does what I am trying to do make any sense?
2) Can a use a canvas for this purpose? If not, what are my other options?
Thanks!
Since you are creating a custom control and creating a template, override the OnApplyTemplate() function in your control's code. There you have to search for template parts and then you get the references in your code.
Something like this.
public class MyControl : Control
{
private Canvas myCanvas;
public override void OnApplyTemplate()
{
Canvas theCanvas = Template.FindName("syringeCanvas", this) as Canvas;
if(theCanvas != null)
{
//<-- Save a reference to the canvas
myCanvas = theCanvas;
//<-- Do some stuff.
}
}
}
Don't forget to name your canvas in the xaml.