I have a transparent canvas on which I can draw arbitrary polylines with the mouse.
Most of the lines are semi-transparent.
Now I need some kind of an eraser tool, i.e. a polyline with an eraser brush, which allows to clear pixels along the mouse movement.
With an opaque canvas I would simply use the background brush but in this case it is Color.FromArgb(0,0,0,0) and drawing with that has no effect.
The canvas seems to be in some kind of alpha blend mode which blends anything I draw on it with what already exists, unless I set the alpha channel to 255 in which case whatever is on the canvas will be overwritten. That does not help me, as I simply want to clear the pixels, i.e. make them fully transparent.
Any ideas?
Here's the main part of the code I'm using:
public class WPFWindow : Window
{
private Canvas canvas = new Canvas();
private bool LDown = false;
private Polyline lines;
private PointCollection points;
public WPFWindow()
{
this.AllowsTransparency = true;
this.WindowStyle = WindowStyle.None;
this.Background = new SolidColorBrush( Color.FromArgb(50,0,0,0) );
this.Width = 500;
this.Height = 400;
this.Top = this.Left = 0;
canvas.Width = this.Width;
canvas.Height = this.Height;
canvas.Background = new SolidColorBrush( Color.FromArgb(0,0,0,0) );
this.Content = canvas;
this.MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler( WPFWindow_MouseLeftButtonDown );
this.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler( WPFWindow_MouseLeftButtonUp );
this.MouseMove += new System.Windows.Input.MouseEventHandler( WPFWindow_MouseMove );
}
void WPFWindow_MouseMove( object sender, System.Windows.Input.MouseEventArgs e )
{
if( LDown )
{
points.Add( e.GetPosition(null) );
}
}
void WPFWindow_MouseLeftButtonUp( object sender, System.Windows.Input.MouseButtonEventArgs e )
{
LDown = false;
}
void WPFWindow_MouseLeftButtonDown( object sender, System.Windows.Input.MouseButtonEventArgs e )
{
LDown = true;
lines = new Polyline();
points = new PointCollection();
lines.Stroke = new SolidColorBrush( Color.FromArgb( 128, 180, 80, 80 ) );
lines.StrokeThickness = 20;
lines.Points = points;
points.Add( e.GetPosition(null) );
canvas.Children.Add( lines );
}
}
The WPF Canvas control is not a drawing surface, it's a "Panel", a container that arranges multiple other controls.
Each Polyline you add to the Canvas is actually a FrameworkElement (a kind of lightweight control) and they are all drawn in order (it's like adding multiple labels or edit controls, there is no way a control can change the visual representation of another control on the window except for covering it up).
You may want to create an actual image draw the polylines on the image and display that image, then you can talk about clearing pixels.
Use an InkCanvas instead of polylines. It has an eraser already implemented
Related
We have to show a legal pop-up in our WPF app. When the pop-up is shown, we use blur effect on the view below.
Recently we recognized that this is causing high GPU usage. Because of the spinner control in the background. The more active content, the more GPU usage.
We collapse this spinner when the pop-up is shown based on a property. But this doesn't help. Only when we set it to collapsed in MainWindow.xaml it works.
We tried multiple things e.g. BitmapCache and other techniques but with no success so far.
Here an example:
https://github.com/rmoergeli/BlurEffectTest.git
I investigated that the problem in demo code hides in your animation: it doesn't stop on changing it visibility from visible to collapsed.
So I found a solution to stop animation on being collapsed with the help of MSDN resource.
public partial class Spinner : UserControl
{
private Canvas _content;
private Storyboard _rotationStoryboard;
public Spinner()
{
// Create a name scope for the page.
NameScope.SetNameScope(this, new NameScope());
DefineContent();
SizeChanged += Spinner_SizeChanged;
IsVisibleChanged += Spinner_IsVisibleChanged;
Loaded += Spinner_Loaded;
}
private void Spinner_Loaded(object sender, RoutedEventArgs e)
{
_rotationStoryboard.Begin(this, isControllable: true);
}
private void Spinner_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool isVisible && isVisible)
_rotationStoryboard.Resume(this);
else
_rotationStoryboard.Pause(this);
}
private void DefineContent()
{
_content = new Canvas();
//set content render transform origin point to center
_content.RenderTransformOrigin = new Point(0.5 , 0.5);
_content.RenderTransform = new RotateTransform(angle: 0);
// Assign the canvas a name by
// registering it with the page, so that
// it can be targeted by storyboard
// animations.
RegisterName("animatableCanvas", _content);
Content = _content;
DefineAnimatableContent();
// Create an animation and a storyboard to animate the
// canvas.
DoubleAnimation doubleAnimation = new DoubleAnimation
{
To = 360,
Duration = new Duration(TimeSpan.FromSeconds(3)),
RepeatBehavior = RepeatBehavior.Forever
};
Storyboard.SetTargetName(doubleAnimation, "animatableCanvas");
Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("RenderTransform.Angle"));
_rotationStoryboard = new Storyboard();
_rotationStoryboard.Children.Add(doubleAnimation);
}
private void DefineAnimatableContent() //renamed Refresh method
{
int n = Balls;
double size = BallSize;
_content.Children.Clear();
double x = ActualWidth / 2;
double y = ActualHeight / 2;
double r = Math.Min(x, y) - size / 2;
double doubleN = Convert.ToDouble(n);
for (int i = 1; i <= n; i++)
{
double doubleI = Convert.ToDouble(i);
double x1 = x + Math.Cos(doubleI / doubleN * 2d * Math.PI) * r - size / 2;
double y1 = y + Math.Sin(doubleI / doubleN * 2d * Math.PI) * r - size / 2;
var e = new Ellipse
{
Fill = BallBrush,
Opacity = doubleI / doubleN,
Height = size,
Width = size
};
Canvas.SetLeft(e, x1);
Canvas.SetTop(e, y1);
_content.Children.Add(e);
};
}
#region Event Handlers
private void Spinner_SizeChanged(object sender, SizeChangedEventArgs e)
{
//we dont need this anymore as we set content render transform origin point to content center
//Transform.CenterX = ActualWidth / 2;
//Transform.CenterY = ActualHeight / 2;
DefineAnimatableContent();
}
#endregion
//other logic
}
This code stops animation on changing Spiner control visibility. But sure you can pause and resume animation with any trigger you need.
N.B! Using this approach you define all the content (not only animarable parts) in code behind so you don't need Spinner.xaml resource anymore.
I'm wondering if there is a way to refresh canvas before adding a child item to it?
I have this code, it draw me a black ellipse with light blue stroke. Now I want to change StrokeThickness when program is launched (I have a slider to define StrokeThickness). The problem is that StrokeThickness is changed but only if I redraw ellipse, but I want that change is made when I move my slider. Any ideas? Thanks!
//this code is in canvas_MouseDown
double smt = sliderThickness.Value;
//krog
elip = new Ellipse
{
Width = 100,
Height = 100,
Fill = Brushes.Black,
Stroke = Brushes.LightBlue,
StrokeThickness = smt,
};
Canvas.SetLeft(elip, mouseX - 50);
Canvas.SetTop(elip, mouseY - 50);
canvas1.Children.Add(elip);
in slider value Changedevent u have to put following code
private void sliderThickness_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
foreach (var item in canvas1.Children)
{
if (item is Ellipse)
{
var elip = item as Ellipse;
elip.StrokeThickness = sliderThickness.Value;
}
}
}
Or shorter
// using System.Linq;
foreach (var ellipse in canvas1.Children.OfType<Ellipse>())
{
ellipse.StrokeThickness = sliderThickness.Value;
}
I am trying to use slider to control the Zoom in and Zoom out of any image.
I have written a code:
private void image1_MouseMove(object sender, MouseEventArgs e)
{
if (!image1.IsMouseCaptured) return;
var tt = (TranslateTransform)((TransformGroup)
image1.RenderTransform).Children.First(tr => tr is TranslateTransform);
Vector v = start - e.GetPosition(border1);
tt.X = origin.X - v.X;
tt.Y = origin.Y - v.Y;
}
Here it is working fine. using mouse scroll. But I want to use the slider for the same functioning.
But unable to put the same behaviour like mouse scroll using slider.
I am very new to WPF and its control, so any help with details is highly appreciated.
How can i implement the same finctionality of Zoom in - out using the slider?
After a lot of research and coding I found that its a very simple application and very simple coding too, to Zoomin and Zoomout the image using a slider. what you need is to put a slider in the area of your choice. and put the following code.
private void slider1_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
TransformGroup transformGroup = (TransformGroup)image1.RenderTransform;
ScaleTransform transform = (ScaleTransform)transformGroup.Children[0];
double zoom = e.NewValue;
transform.ScaleX = zoom;
transform.ScaleY = zoom;
}
Now I already have the code so that the image will Zoomin and Zoomin using the mousewheel.
private void image1_MouseWheel(object sender, MouseWheelEventArgs e)
{
TransformGroup transformGroup = (TransformGroup)image1.RenderTransform;
ScaleTransform transform = (ScaleTransform)transformGroup.Children[0];
double zoom = e.Delta > 0 ? .2 : -.2;
transform.ScaleX += zoom;
transform.ScaleY += zoom;
slider1.Value += zoom;
}
Now when you put
slider1.value += zoom;
Then changes in the mousewheel and in the changes in the slider change the zoom of the picture.
Remember the difference of implementation between mouse wheel and slider. In mouse wheel MouseWheelEventsArgs "e" and e.Delte value is positive and negative depends on the mousewheel up or mousewheel down. But in Slider RoutedPropertyChangesEventsArgs value is positive or negative according to the sldier up and down. You don't need to specify here. Just
zoom = e.NewValue;
I hope it will help.
You have to code a ScaleTransform in your code. This is a part of my code that do this, I don't put everything but it's a base for you.
For the slider, you have to bind the Zoom property to the Value of the Slider.
private double m_dCurZoom = 1.0;
private ScaleTransform m_transZoom;
public ScaleTransform TransZoom
{
get { return m_transZoom; }
}
private TranslateTransform m_transPan;
public double Zoom
{
get { return m_dCurZoom; }
set
{
double oldzoom = m_dCurZoom;
if (m_dCurZoom != value)
{
m_dCurZoom = value;
OnPropertyChanged("Zoom");
UpdateZoom(oldzoom);
}
}
}
public void UpdateZoom(double oldzoom)
{
// Are we visible?
if (m_root == null)
return;
// Get parent
FrameworkElement parent = GetRootParent();
if (parent == null)
return;
// Center point of the window
Point ptCenter = new Point(parent.RenderSize.Width / 2, parent.RenderSize.Height / 2);
// Translate into canvas coordinates
ptCenter = m_root.TranslatePoint(ptCenter, m_canvas);
// Update the zoom
m_transZoom.ScaleX = m_dCurZoom;
m_transZoom.ScaleY = m_dCurZoom;
m_transPan.X -= ptCenter.X * m_dCurZoom - ptCenter.X * oldzoom;
m_transPan.Y -= ptCenter.Y * m_dCurZoom - ptCenter.Y * oldzoom;
ResizeElementContents();
OnPropertyChanged("Zoom");
}
The functionality: Users can draw rectangles on a defined area of the screen, by clicking the left mouse button and dragging to create the desired sized rectangle. A similar example would be re-sizing a rectangle in BLEND. I am also open to options where the rectangle already exists, and users can resize using a drag/resize handle.
Ideas on how this might be accomplished?
Drawing rectangles on to a canvas can be done fairly easily:-
<Canvas x:Name="draw" Background="Transparent" MouseLeftButtonDown="draw_MouseLeftButtonDown" />
then:-
Point origPoint;
Rectangle rect;
void draw_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
rect = new Rectangle();
origPoint = e.GetPosition(draw);
Canvas.SetLeft(rect, origPoint.X);
Canvas.SetTop(rect, origPoint.Y);
rect.Stroke = new SolidColorBrush(Colors.Black);
rect.StrokeThickness = 2;
draw.Children.Add(rect);
draw.MouseMove += draw_MouseMove;
draw.MouseLeftButtonUp += draw_MouseLeftButtonUp;
}
void draw_MouseMove(object sender, MouseEventArgs e)
{
if (rect != null)
{
Point curPoint = e.GetPosition(draw);
if (curPoint.X > origPoint.X)
{
rect.Width = curPoint.X - origPoint.X;
}
else if (curPoint.X < origPoint.X)
{
Canvas.SetLeft(rect, curPoint.X);
rect.Width = origPoint.X - curPoint.X;
}
if (curPoint.Y > origPoint.Y)
{
rect.Height = curPoint.Y - origPoint.Y;
}
else if (curPoint.Y < origPoint.Y)
{
Canvas.SetTop(rect, curPoint.Y);
rect.Height = origPoint.Y - curPoint.Y;
}
}
}
void draw_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (rect != null)
{
draw.MouseMove -= draw_MouseMove;
draw.MouseLeftButtonUp -= draw_MouseLeftButtonUp;
rect = null;
}
}
Whether this can actually full fill your requirements I'm not sure. It would really depend on what you might have underneath the canvas and what you wanted to do with the rectangles once drawn.
It seems like a simple question but how do I set the bacground color of the 'tab control', it seems to be derived from the standard window theme color. Is it Possible to create a black tab control with white text written on the tabs themselves (not the tab page)?
Help, I,m a little familiar with custom controls extending existing controls but I don't know what properties (if they exist) to set.
http://dotnetrix.co.uk/tabcontrol.htm
private void tabControl1_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e)
{
TabPage CurrentTab = tabControl1.TabPages[e.Index];
Rectangle ItemRect = tabControl1.GetTabRect(e.Index);
SolidBrush FillBrush = new SolidBrush(Color.Red);
SolidBrush TextBrush = new SolidBrush(Color.White);
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
//If we are currently painting the Selected TabItem we'll
//change the brush colors and inflate the rectangle.
if (System.Convert.ToBoolean(e.State & DrawItemState.Selected))
{
FillBrush.Color = Color.White;
TextBrush.Color = Color.Red;
ItemRect.Inflate(2, 2);
}
//Set up rotation for left and right aligned tabs
if (tabControl1.Alignment == TabAlignment.Left || tabControl1.Alignment == TabAlignment.Right)
{
float RotateAngle = 90;
if (tabControl1.Alignment == TabAlignment.Left)
RotateAngle = 270;
PointF cp = new PointF(ItemRect.Left + (ItemRect.Width / 2), ItemRect.Top + (ItemRect.Height / 2));
e.Graphics.TranslateTransform(cp.X, cp.Y);
e.Graphics.RotateTransform(RotateAngle);
ItemRect = new Rectangle(-(ItemRect.Height / 2), -(ItemRect.Width / 2), ItemRect.Height, ItemRect.Width);
}
//Next we'll paint the TabItem with our Fill Brush
e.Graphics.FillRectangle(FillBrush, ItemRect);
//Now draw the text.
e.Graphics.DrawString(CurrentTab.Text, e.Font, TextBrush, (RectangleF)ItemRect, sf);
//Reset any Graphics rotation
e.Graphics.ResetTransform();
//Finally, we should Dispose of our brushes.
FillBrush.Dispose();
TextBrush.Dispose();
}
I use something like this in mu TabControl derived class (and it will do gradients too):
protected override void OnDrawItem(DrawItemEventArgs e)
{
// fill in the whole rect
using (SolidBrush br = new SolidBrush(Theme.FormBackColor))
{
e.Graphics.FillRectangle(br, ClientRectangle);
}
// draw the tabs
for (int i = 0; i < TabPages.Count; ++i)
{
TabPage tab = TabPages[i];
// Get the text area of the current tab
RectangleF tabTextArea = (RectangleF)GetTabRect(i);
// determine how to draw the tab based on which type of tab it is
Color tabTopBackColor = GetTopBackColor();
Color tabBottomBackColor = GetBottomBackColor();
Color tabTextColor = GetTextColor();
// draw the background
using (LinearGradientBrush br = new LinearGradientBrush(tabTextArea, tabTopBackColor, tabBottomBackColor, LinearGradientMode.Vertical))
{
e.Graphics.FillRectangle(br, tabTextArea);
}
// draw the tab header text
using (SolidBrush brush = new SolidBrush(tabTextColor))
{
e.Graphics.DrawString(tab.Text, Font, brush, CreateTabHeaderTextRect(tabTextArea));
}
}
}
private RectangleF CreateTabHeaderTextRect(RectangleF tabTextArea)
{
tabTextArea.X += 3;
tabTextArea.Y += 1;
tabTextArea.Height -= 1;
return tabTextArea;
}