After doing some research on subject I didn't find anything, so I'm sorry if the same question was already asked.
Task: make a colored track-line on Canvas after cursor, when the left mouse button is pressed (like brush in Paint).
Problem: I think using System.Windows.Shapes.Path is the best approach to doing this task. Code below works fine, except for one thing: if you try to move your cursor then change direction to the opposite (e.g. the value on X-axis increases, then decreases, but the value on Y-axis, stays constant), you will get an unexpected part of Line, corresponding to the previous direction.
I'm sorry for the tangled description of my problem, but I hope you will get it.
To make it easier for you to reproduce it on your machine I'm adding the solution.
Please, point out for my mistake if I did one!
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Boolean Inserting;
private Path path;
private Boolean isFirstPoint;
public MainWindow()
{
InitializeComponent();
LolCanvas.IsHitTestVisible = true;
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (Inserting)
{
Point p = Mouse.GetPosition(LolCanvas);
if (isFirstPoint)
{
PathFigure myPathFigure = new PathFigure();
myPathFigure.StartPoint = new Point(p.X + 5, p.Y + 5);
myPathFigure.Segments = new PathSegmentCollection();
(path.Data as PathGeometry).Figures.Add(myPathFigure);
isFirstPoint = false;
}
else
{
LineSegment myLineSegment = new LineSegment();
myLineSegment.Point = new Point(p.X + 5, p.Y + 5);
(path.Data as PathGeometry).Figures[0].Segments.Add(myLineSegment);
}
}
}
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
Inserting = true;
path = new Path();
path.Stroke = new SolidColorBrush(Colors.Red);
path.StrokeThickness = 50;
path.Data = new PathGeometry();
(path.Data as PathGeometry).Figures = new PathFigureCollection();
LolCanvas.Children.Add(path);
isFirstPoint = true;
}
private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
Inserting = false;
}
}
}
Xaml:
<Window x:Class="WpfApplication3.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">
<Canvas x:Name="LolCanvas" MouseMove="Canvas_MouseMove" MouseDown="Canvas_MouseDown" MouseUp="Canvas_MouseUp" Background="Black">
</Canvas>
</Window>
Link to the application: http://ge.tt/99aSgyo/v/0?c
Apparently this kind of behavior is correct for path. The problem appeared because of the angle between line parts. It was 180 degrees, so window couldn't render Path propertly in this place.
I had two ways to defeat it:
1) Set IsSmoothJoin property to true for each line segment.
2) Make another Path object, when this kind of problem might occur
Related
My WPF application has a button that when pressed, opens notepad.
Now i need to open notepad in x, y coordinates, how do i do that?
I basically want to open my program, then open notepad and place it in (500,1000) (x, y).
<Window x:Class="MoveWindow.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:MoveWindow"
mc:Ignorable="d"
Title="MainWindow" WindowStartupLocation="Manual" Height="350" Width="500">
<FrameworkElement Width="110" />
</Window>
This is the xaml.cs part:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace MoveWindow
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Left = 0;
Top = 0;
// Process.Start("notepad.exe");
}
}
}
You can use (#as rehan said), MoveWindow.
Check this solution (in your case would be):
public MainWindow()
{
InitializeComponent();
var notepadProcess = Process.Start("notepad.exe");
if (notepadProcess != null)
{
notepadProcess.WaitForInputIdle();
// positioning at x=100, y=100 with width of: 500 and height of: 200
CustomMove(notepadProcess, 100, 100, 500, 200);
}
}
public void CustomMove(Process process, int x, int y, int width, int height)
{
var ok = MoveWindow(process.MainWindowHandle, x, y, width, height, true);
if (ok == false)
MessageBox.Show("Couldn't move your window!");
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int width, int height, bool repaint);
The first parameter of MoveWindow is the Window Handler pointer (which is your process's handle).
The second & third is x and y position on your screen
The fourth & fifth is width and height of your window
The sixth parameter :
Indicates whether the window is to be repainted. If this parameter is TRUE, the window receives a message. If the parameter is FALSE, no repainting of any kind occurs. This applies to the client area, the nonclient area (including the title bar and scroll bars), and any part of the parent window uncovered as a result of moving a child window.
However if you need more details about this method, checkout this link: https://msdn.microsoft.com/en-us/library/windows/desktop/ms633534(v=vs.85).aspx
UPDATE
MainWindow class
public partial class MainWindow : Window
{
private readonly Process notepadProcess = null;
public MainWindow()
{
InitializeComponent();
notepadProcess = Process.Start("notepad.exe");
if (notepadProcess != null)
{
notepadProcess.WaitForInputIdle();
CustomMove(notepadProcess, (int) Application.Current.MainWindow.Top, (int) Application.Current.MainWindow.Left, 500, 200);
}
}
public void CustomMove(Process process, int x, int y, int width, int height)
{
var ok = MoveWindow(process.MainWindowHandle, x, y, 300, 200, true);
if (ok == false)
MessageBox.Show("Couldn't move your window!");
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int width, int height, bool repaint);
private void Window_LocationChanged(object sender, EventArgs e)
{
CustomMove(notepadProcess, (int)Application.Current.MainWindow.Top, (int)Application.Current.MainWindow.Left, 500, 200);
}
}
MainWindow.xaml
<Window x:Class="WpfApplication2.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"
LocationChanged="Window_LocationChanged">
<Window.Resources>
</Window.Resources>
<Grid>
</Grid>
</Window>
I've got a requirement to create several shapes based on a supplied size (all of them have the same height/width) and have their sizes be databound to that supplied property on the datacontext.
Most of the shapes are easy: Circle (ellipse with height/width bound), square (rectangle with height/width bound), diamond (same as square, then use a RotateTransform), + (two lines), X (two lines).
But I'm trying to figure out how to do it for a triangle and I can't figure it out. It needs to be a filled object, so I can't just do it with three lines.
But all of the ways i've seen to do it (w/ a Path or a Polygon) end up taking Point objects (StartPoint, EndPoint, etc). And you can't bind to the X or Y values of the Point object.
Am I missing something? Or do I need to write my own custom shape or something?
Edit: To add a little bit of clarity... the type of triangle I'm creating doesn't really matter. It can be equilateral or isosceles. I was targeting an isosceles, so that it would have a base with the databound width and the top "tip" of the triangle will be at the mid-point of the databound width and at Y=0. That was just an optimization for simplicity's sake
The behavior class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Shapes;
using System.Windows.Media;
namespace WpfApplication1
{
public enum ShapeType
{
Rectangle,
Isosceles,
Ellipse,
Dice,
Hexagon
}
public class PathControl
{
public static readonly DependencyProperty ShapeTypeProperty =
DependencyProperty.RegisterAttached("ShapeType",
typeof(ShapeType?),
typeof(DependencyObject),
new PropertyMetadata(null,
new PropertyChangedCallback((sender, args) =>
{
Path path = sender as Path;
ShapeType? shapeType = (ShapeType?)args.NewValue;
//todo: use a WeakEvent
path.SizeChanged +=
(pathSender, pathArgs) =>
{
PathControl.InvalidatePath((Path)sender, shapeType);
};
})));
private static void InvalidatePath(Path path, ShapeType? shapeType)
{
if (path != null
&& shapeType.HasValue)
{
string source = null;
double netWidth = path.Width - 2 * path.StrokeThickness,
netHeight = path.Height - 2 * path.StrokeThickness;
if (shapeType == ShapeType.Rectangle)
{
source = string.Format("M0,0 h{0} v{1} h-{0} z",
new object[2]
{
netWidth,
netHeight
});
}
else if (shapeType == ShapeType.Isosceles)
{
source = string.Format("M0,{1} l{0},-{1} {0},{1} z",
new object[2]
{
netWidth / 2,
netHeight
});
}
else
{
throw new NotImplementedException(shapeType.ToString());
}
path.Data = Geometry.Parse(source);
}
}
public static void SetShapeType(DependencyObject o, ShapeType e)
{
o.SetValue(PathControl.ShapeTypeProperty, e);
}
public static ShapeType? GetShapeType(DependencyObject o)
{
return (ShapeType)o.GetValue(PathControl.ShapeTypeProperty);
}
}
}
The XAML:
<Window x:Class="WpfApplication1.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:local="clr-namespace:WpfApplication1">
<Grid>
<Path Width="100" Height="100" Stroke="Green" StrokeThickness="2" Fill="Yellow"
local:PathControl.ShapeType="Isosceles">
<Path.RenderTransform>
<RotateTransform Angle="90"></RotateTransform>
</Path.RenderTransform>
</Path>
</Grid>
</Window>
Binding to the Points is the best/only way. The X and X properties of a Point cannot be bound to because they do not raise the PropertyChanged event. The Point is a structure and structures should be read-only.
The PointCollection class raises the correct events so you can bind to it. This allows you to manipulate the triangles by modifying the collection of point by replacing the points. Do not change the point but replace them so the proper events will be raised.
I've noticed that there is a difference in the time it takes for a WPF Progress Bar and a WinForms Progress Bar to fill completely.
Fill completely as in set the Value to 100 in both Forms and WPF, one can notice that WinForms fills the bar smoothly whereas the WPF fills it instantly.
I wanted to know if there is a property that we can edit in the templates to change that.
Hope I made it clear, I can post a video too if anyone wants.
EDIT
Here's a video of what I'm talking about, notice the difference ?
EDIT 2
Filling the progress bar with a timer ?
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
namespace WpfApplication2
{
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
this.Title = "WPF Progress Bar Demo";
}
private void fill(int from, int to)
{
Duration duration = new Duration(TimeSpan.FromSeconds(0.5));
DoubleAnimation doubleanimation = new DoubleAnimation(from, to, duration);
progb.BeginAnimation(ProgressBar.ValueProperty, doubleanimation);
}
private void fill_Click(object sender, RoutedEventArgs e)
{
fill(0, 100);
}
}
}
Is that OK and will it work anywhere ?
Feel free to change it.
Thanks.
The idea is that a progress bar reports actual progress - not time elapsed. It's not intended to be an animation that just indicates something is happening.
The basic principle is that you bind Value to a property on your DataContext class, and update that value whenever a progress milestone occurs.
You can make it fill at a specified rate using a timer - here is an example:
<Window x:Class="WpfApplication3.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">
<Grid>
<ProgressBar Value="{Binding Path=ProgressValue}"></ProgressBar>
</Grid>
And the code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
Timer timer;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
timer = new Timer();
timer.Interval = 1000;
timer.Elapsed += new ElapsedEventHandler(t_Elapsed);
timer.Start();
}
void t_Elapsed(object sender, ElapsedEventArgs e)
{
if (this._progressValue < 100)
this.ProgressValue = _progressValue + 10;
else
{
timer.Stop();
timer.Dispose();
}
}
private double _progressValue;
public double ProgressValue
{
get { return _progressValue; }
set
{
_progressValue = value;
RaisePropertyChanged("ProgressValue");
}
}
private void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
See my answer on How to update a progress bar so it increases smoothly?
It's similar to the extension method, but uses a behavior so that you can decouple the progress bar from the thing that's reporting progress. :)
It looks like it's a problem (or not) with only WPF progress bar...another user reported it here
WPF Control, exactly progress bar, does not update itself when
copying When I test to copy a big file, the complete GUI just
completely freezes. The progress bar doesn’t run smoothly. It just
jumps from 0 to 100.
It was solved by adding an extension method:
//Your Code
pbBar.Value = some_value;
pbBar.Refresh();
//Your Code
public static class ExtensionMethods
{
private static Action EmptyDelegate = delegate() { };
public static void Refresh(this UIElement uiElement)
{
uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
}
public static void RefreshInput(this UIElement uiElement)
{
uiElement.Dispatcher.Invoke(DispatcherPriority.Input, EmptyDelegate);
}
}
Calling the Refresh() method after setting the value solved the issue.
But, what I found was even after applying the refresh() method, the progress bar jumps on each run (from different values).
Using a backgroundworker and reportprogress gives the exact result with no "jumps".
My first WPF application, but it is not working. Help please!
xaml:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="308" Width="527">
<Grid Name="canvas">
<Canvas></Canvas>
</Grid>
</Window>
Code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Line line = new Line();
line.X1 = 0;
line.Y1 = 100;
line.X2 = 0;
line.Y2 = 100;
line.Stroke = Brushes.Red;
line.StrokeThickness = 1; // Note1
canvas.Children.Insert(0, line);
}
}
}
What I see is your first X,Y coordinate and second are the same. So the line being drawn is over the same point.
line.X1 = 0;
line.Y1 = 100;
line.X2 = 0;
line.Y2 = 100;
// Change too this and that will will draw straight over 100 pixels.
line.X1 = 0;
line.Y1 = 100;
line.X2 = 100;
line.Y2 = 100;
Your X1/Y1 values are the same as the X2/Y2 values. If you change line.X2 = 0; to line.X2 = 50;, you'll see your line.
If your line isn't going to be dynamic though, it's generally best practice to do most visual stuff in XAML directly like so:
<Grid Name="canvas">
<Line X1="0" Y1="100" X2="50" Y2="100" StrokeThickness="1" Stroke="Red" />
</Grid>
Hope this helps,
Andy
It does work.
But you are creating a single point not a line, and adding it to the grid, not the canvas. In fact, I don't think you'll even see a point with the start and end points being the same.
Change X2 to 300 and you'll see a red line.
SergioL
I'm trying to write a simple WPF app that has two ellipses, joined by a line, like you might see in a network graph. When the ellipses are animated, I just want the joining line to automagically 'stick' to the canvas locations of the two ellipses that the line joins. The XAML is just a canvas:
<Window x:Class="UIDataBindingDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="400" Width="400">
<Grid>
<Canvas x:Name="cnvExample" />
</Grid>
...and I'm just doing some really simple stuff in the constructor here:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace UIDataBindingDemo
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
// create 2 ellipses, one next to the other, and add them to the canvas
Ellipse el1 = new Ellipse();
Canvas.SetTop(el1, 100);
Canvas.SetLeft(el1, 100);
el1.Width = 20;
el1.Height = 20;
el1.Fill = Brushes.Red;
el1.Stroke = Brushes.Black;
Ellipse el2 = new Ellipse();
Canvas.SetTop(el2, 100);
Canvas.SetLeft(el2, 200);
el2.Width = 20;
el2.Height = 20;
el2.Fill = Brushes.Blue;
el2.Stroke = Brushes.Black;
cnvExample.Children.Add(el1);
cnvExample.Children.Add(el2);
// create a line that connects the 2 ellipses. Bind the two points that define this line to the
// locations of our ellipses, so the line always connects them, through animations, drag and drop
// operations, whatever.
Line line = new Line();
line.StrokeThickness = 3;
line.Stroke = Brushes.Black;
line.SetBinding(Line.X1Property, new Binding("(Canvas.Left)") { Source = el1 });
line.SetBinding(Line.X1Property, new Binding("(Canvas.Top)") { Source = el1 });
line.SetBinding(Line.X1Property, new Binding("(Canvas.Left)") { Source = el2 });
line.SetBinding(Line.X1Property, new Binding("(Canvas.Top)") { Source = el2 });
cnvExample.Children.Add(line);
// animate the second ellipse, so it moves down and to the right, nice and slow
var moveTheBlueOne = new DoubleAnimation(300, TimeSpan.FromSeconds(10));
el2.BeginAnimation(Canvas.LeftProperty, moveTheBlueOne);
el2.BeginAnimation(Canvas.TopProperty, moveTheBlueOne);
}
}
I'm pretty new to WPF, and I'm sure I'm missing something simple. Why am I not seeing the line?
I don't know if it's a cut and paste error but youre assigning each binding to the same DependencyProperty "Line.X1Property", you should use all four X and Y properties to define a starting point and an ending one for a line.
line.SetBinding(Line.X1Property, new Binding("(Canvas.Left)") { Source = el1 });
line.SetBinding(Line.Y1Property, new Binding("(Canvas.Top)") { Source = el1 });
line.SetBinding(Line.X2Property, new Binding("(Canvas.Left)") { Source = el2 });
line.SetBinding(Line.Y2Property, new Binding("(Canvas.Top)") { Source = el2 });
this way it works for me.