I would like to draw a graph of a stock on a canvas.
The canvas has an initil size lets say 300 (height) x 1000 (Width).
The stock has been trading for more days that can fit into the visual part of the Canvas
I would like to drag (scroll) the visual part of the canvas around and see the non visual parts that are hidden "behind". i.e see trading days that are not initially displyed.
Would appreciate any ideas or pointers to get me on the right path.
You can use two canvases - a canvas inside a canvas. The outer canvas would be fixed in size (1000 x 300), but the inner canvas would be as large as you want it to be. Set the ClipToBounds property of the outer canvas to True.
C#:
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.Shapes;
namespace Stock
{
/// <summary>
/// Interaction logic for ListBox.xaml
/// </summary>
public partial class Chart : Window
{
private Point _capturePoint;
private double _captureLeft;
private double _captureTop;
public Chart()
{
InitializeComponent();
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
_capturePoint = e.GetPosition(chart);
_captureLeft = Canvas.GetLeft(chart);
_captureTop = Canvas.GetTop(chart);
if (_capturePoint.X > 0 && _capturePoint.X < chart.RenderSize.Width &&
_capturePoint.Y > 0 && _capturePoint.Y < chart.RenderSize.Height)
{
CaptureMouse();
}
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
ReleaseMouseCapture();
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (Mouse.Captured == this)
{
var currentPoint = e.GetPosition(chart);
var diff = Point.Subtract(currentPoint, _capturePoint);
Canvas.SetTop(chart, _captureTop + diff.Y);
Canvas.SetLeft(chart, _captureLeft + diff.X);
}
}
}
}
XAML:
<Window x:Class="Stock.Chart"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListBox" Height="300" Width="300">
<Canvas Height="300" Width="1000" x:Name="viewport" ClipToBounds="True">
<Canvas x:Name="chart" Background="Green" Width="2000" Height="1000" Canvas.Top="20" Canvas.Left="10" >
</Canvas>
</Canvas>
</Window>
Related
In the file MainWindow.xaml.cs in the method TakeScreenShot i'm getting exception on the
line :
var executedScript = WebView.EvaluateScriptAsync(jsString).Result.Result;
System.Exception
HResult=0x80131500
Message=Unable to execute javascript at this time, scripts can only be executed within a V8Context. Use the IWebBrowser.CanExecuteJavascriptInMainFrame property to guard against this exception. See https://github.com/cefsharp/CefSharp/wiki/General-Usage#when-can-i-start-executing-javascript for more details on when you can execute javascript. For frames that do not contain Javascript then no V8Context will be created. Executing a script once the frame has loaded it's possible to create a V8Context. You can use browser.GetMainFrame().ExecuteJavaScriptAsync(script) or browser.GetMainFrame().EvaluateScriptAsync to bypass these checks (advanced users only).
Source=CefSharp
StackTrace:
at CefSharp.WebBrowserExtensions.ThrowExceptionIfCanExecuteJavascriptInMainFrameFalse()
at CefSharp.WebBrowserExtensions.EvaluateScriptAsync(IChromiumWebBrowserBase browser, String script, Nullable`1 timeout, Boolean useImmediatelyInvokedFuncExpression)
at Web_Browser_WPF.MainWindow.TakeScreenShot(String siteUrl) in D:\Csharp Projects\Web Browser WPF\MainWindow.xaml.cs:line 47
at Web_Browser_WPF.MainWindow.ChromiumWebBrowser_LoadingStateChanged(Object sender, LoadingStateChangedEventArgs e) in D:\Csharp Projects\Web Browser WPF\MainWindow.xaml.cs:line 64
at CefSharp.Wpf.ChromiumWebBrowser.CefSharp.Internals.IWebBrowserInternal.SetLoadingStateChange(LoadingStateChangedEventArgs args)
at CefSharp.Internals.ClientAdapter.OnLoadingStateChange(ClientAdapter* , scoped_refptr* browser, Boolean isLoading, Boolean canGoBack, Boolean canGoForward)
and then if i mark as comment not to use the part of the jString and executedScript and not assigning to the height then i'm getting null exception on the line :
bitmap.Save(#"d:\Test.jpg");
because WebView is null.
so i'm stuck here.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
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;
using CefSharp;
using CefSharp.OffScreen;
namespace Web_Browser_WPF
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
WindowStartupLocation =
System.Windows.WindowStartupLocation.CenterScreen;
}
private void TakeScreenShot(string siteUrl)
{
CefSharp.OffScreen.ChromiumWebBrowser WebView = new CefSharp.OffScreen.ChromiumWebBrowser(siteUrl);
int width = 1280;
int height = 1480;
string jsString = "Math.max(document.body.scrollHeight, " +
"document.documentElement.scrollHeight, document.body.offsetHeight, " +
"document.documentElement.offsetHeight, document.body.clientHeight, " +
"document.documentElement.clientHeight);";
var executedScript = WebView.EvaluateScriptAsync(jsString).Result.Result;
height = Convert.ToInt32(executedScript);
WebView.Size = new System.Drawing.Size(width, height); //size;
Thread.Sleep(500);
// Wait for the screenshot to be taken.
var bitmap = WebView.ScreenshotOrNull();
bitmap.Save(#"d:\Test.jpg");
bitmap.Dispose();
}
private void ChromiumWebBrowser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
{
if(!e.IsLoading)
{
TakeScreenShot("https://ims.gov.il/he/RadarSatellite");
}
}
}
}
The xaml code
<Window
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:cef="clr-namespace:Web_Browser_WPF"
xmlns:Wpf="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf" x:Class="Web_Browser_WPF.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="720" Width="1024">
<Grid>
<Wpf:ChromiumWebBrowser HorizontalAlignment="Left" Margin="363,181,0,0" VerticalAlignment="Top"/>
<Wpf:ChromiumWebBrowser x:Name="chromiumBrowser1"/>
<Wpf:ChromiumWebBrowser Address="https://ims.gov.il/he/RadarSatellite" LoadingStateChanged="ChromiumWebBrowser_LoadingStateChanged" />
</Grid>
</Window>
I am a reeeally noob beginner WPF developer, and getting the hang of c#.
I am creating an app, where I need a knob Button and a TextBox Display, where the knob adjusts the text from the display, and the display, if text is changed, updates the knob position.
Inage example of my application
I've managed to create the Knob Button, which spins when clicked and dragged, and also managed to bind it's value to the TextBox, it displays the value perfectly, but I can't make the TextBox Text update the Knob's position, which is defined by Angle variable (from the RotateTransform thing), the code is as follows:
<UserControl x:Class="quaselaeuespero.VolumeControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:quaselaeuespero"
mc:Ignorable="d"
d:DesignHeight="154" d:DesignWidth="148">
<Grid>
<Image Name="aPorradoknob" Source="Knob.png" RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<RotateTransform Angle="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:VolumeControl}}, Path=Angle}"/>
</Image.RenderTransform>
</Image>
</Grid>
</UserControl>
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 quaselaeuespero
{
/// <summary>
/// Interaction logic for VolumeControl.xaml
/// </summary>
public partial class VolumeControl : UserControl
{
public static readonly DependencyProperty AngleProperty =
DependencyProperty.Register("Angle", typeof(double), typeof(VolumeControl), new UIPropertyMetadata(0.0));
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
public VolumeControl()
{
InitializeComponent();
this.Angle = 120;
this.MouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown);
this.MouseUp += new MouseButtonEventHandler(OnMouseUp);
this.MouseMove += new MouseEventHandler(OnMouseMove);
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(this);
}
private void OnMouseUp(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(null);
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (Mouse.Captured == this)
{
// Get the current mouse position relative to the volume control
Point currentLocation = Mouse.GetPosition(this);
// We want to rotate around the center of the knob, not the top corner
Point knobCenter = new Point(this.ActualHeight / 2, this.ActualWidth / 2);
// Calculate an angle
double radians = Math.Atan((currentLocation.Y - knobCenter.Y) /
(currentLocation.X - knobCenter.X));
this.Angle = radians * 180 / Math.PI;
// Apply a 180 degree shift when X is negative so that we can rotate
// all of the way around
if (currentLocation.X - knobCenter.X < 0)
{
this.Angle += 180;
}
if(this.Angle >= -90 && this.Angle <= -45)
{
this.Angle = 270;
}
if (this.Angle >= -45 && this.Angle <= 0)
{
this.Angle = 1;
}
this.Angle = Math.Round(this.Angle, 1);
}
}
}
}
The Knob is <VolumeControl/> and Display is <DisplayBPM/>, in the main Window I tried to bind them both:
<Window x:Class="quaselaeuespero.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:quaselaeuespero"
mc:Ignorable="d"
Title="MainWindow" Height="540" Width="960">
<Grid>
<Grid.Background>
<ImageBrush ImageSource="Background.png"/>
</Grid.Background>
<local:VolumeControl x:Name="Knobão" Margin="123,240,675,111" RenderTransformOrigin="0.5,0.5" Angle="{Binding ElementName=BPMDisplay, Path=BPM, UpdateSourceTrigger=Explicit}"/>
<local:DisplayBPM x:Name="BPMDisplay" BPM="{Binding ElementName=Knobão, Path=Angle, UpdateSourceTrigger=Explicit}" Margin="68,153,656,274" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</Window>
The following is the code for DisplayBPM:
<UserControl x:Class="quaselaeuespero.DisplayBPM"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:quaselaeuespero"
mc:Ignorable="d"
d:DesignHeight="79
" d:DesignWidth="229"
Name="Display">
<Grid Margin="0">
<TextBox x:Name="BPMTexto" Text="{Binding ElementName=Display, Path=BPM}" HorizontalAlignment="Right" Margin="0,0,4,0" Width="222" BorderBrush="{x:Null}" SelectionBrush="{x:Null}" Foreground="#FFCF1D1D" FontSize="80" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontFamily="DS-Digital" RenderTransformOrigin="0.5,0.5" CaretBrush="#FFCF1D1D">
<TextBox.Background>
<ImageBrush ImageSource="Display BPM.png" Stretch="Uniform"/>
</TextBox.Background>
</TextBox>
</Grid>
</UserControl>
and the DisplayBPM.xaml.cs:
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 quaselaeuespero
{
/// <summary>
/// Interaction logic for DisplayBPM.xaml
/// </summary>
public partial class DisplayBPM : UserControl
{
private void BPMTexto_TextChanged(object sender, EventArgs e)
{
BPM = Convert.ToDouble(BPMTexto.Text);
}
public static readonly DependencyProperty BPMProperty =
DependencyProperty.Register("BPM", typeof(double), typeof(DisplayBPM), new UIPropertyMetadata(0.0));
public double BPM
{
get { return (double)GetValue(BPMProperty); }
set { SetValue(BPMProperty, value); }
}
public DisplayBPM()
{
InitializeComponent();
BPM = 1;
}
}
}
The problem is that BPM (variable from DisplayBPM, from which TextBox gets its input) doesn't seem to be changed, and if it is changed, it is not changing Angle (variable from RotateTransform that determines the Knob position). Can anyone help me? I know there are probably tons of basic problems, it would really help me if you could explain them to me. Thank you so much!
To start, making a custom user control that has Dependency Properties is not the solution for every problem in WPF.
WPF Apps are primarily architected with MVVM: Model - View - ViewModel
With that stated for your specific need I would keep the VolumeControl as that is the correct way to create custom UserControls that have custom DependencyProperties
I would then delete the DisplayBPM class as it is not needed.
I would setup a ViewModel to interact between your controls that contains a single BPM string property.
Here is an example ViewModel I would use:
public class MainWindowViewModel : INotifyPropertyChanged
{
private string _bpm;
public string BPM
{
get => _bpm;
set
{
_bpm = value;
RaisePropertyChanged(nameof(BPM));
}
}
public void RaisePropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public event PropertyChangedEventHandler PropertyChanged;
}
As a side note, I would suggest reading up on INotifyPropertyChanged
as there are many libraries out there that you can use to help with
WPF and MVVM
I would then setup the Window with the VolumeControl and just a TextBox to hold the BPM value. Both of these should have a {Binding BPM, Mode=TwoWay} so that you pass the BPM value between controls.
You can then decide on the TextBox binding if you want the value to take as the user is typing or after the user leaves the field (usually with the Tab key). To have the value update the VolumeControl as the user is typing have UpdateSourceTrigger=PropertyChanged in the binding.
See my example here:
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.Background>
<ImageBrush ImageSource="Background.png"/>
</Grid.Background>
<local:VolumeControl
x:Name="Knobão"
Margin="123,240,675,111"
RenderTransformOrigin="0.5,0.5"
Angle="{Binding BPM, Mode=TwoWay}" />
<TextBox
Text="{Binding BPM, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="68,153,656,274"
MinWidth="222"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<TextBox.Background>
<ImageBrush
ImageSource="Display BPM.png"
Stretch="Uniform" />
</TextBox.Background>
</TextBox>
</Grid>
If you are not familiar with how ViewModels and DataContext work within WPF I would recommend reading up on that as that is the primary way to setup an MVVM architecture for WPF apps.
I need to expand an existing Run to include some new text (with different formatting) without adding an additional paragraph. Is this possible?
When I inspect the properties of the FirstBlock on the Document, I do not see any property which allow me to drill down into the paragraph so that I can add a Run to it.
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 WpfApp3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.flowdoc.Document = new FlowDocument();
Run r = new Run("Hello ");
r.Background = new SolidColorBrush(Colors.Yellow);
r.FontSize = 14;
Paragraph p = new Paragraph(r);
flowdoc.Document.Blocks.Add(p);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Run r = new Run("World");
r.Background = new SolidColorBrush(Colors.LightCyan);
//Append run to existing run
//
}
}
}
<Window x:Class="WpfApp3.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:WpfApp3"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<FlowDocumentReader Grid.Column="0" x:Name="flowdoc"></FlowDocumentReader>
<Button Grid.Column="1" Content="append" Click="Button_Click"></Button>
</Grid>
</Window>
To get your Paragraph you may iterate the Blocks property of the document.
You can then easily add a new Run to the Block's Inlines collection.
private void Button_Click(object sender, RoutedEventArgs e)
{
Run r = new Run("World");
r.Background = new SolidColorBrush(Colors.LightCyan);
//Append run to existing run
var p = flowdoc.Document.Blocks.OfType<Paragraph>().First();
p.Inlines.Add(r);
}
I have a ColumnSeries with data bound to it and it displays correctly. When a column is right-clicked, I want to figure out what the independent-axis (X) value is. Ideally, I want to display a context menu with that information.
I have a MouseRightButtonDown handler, but cannot figure out how to perform the hit test to get the X-axis information.
I have selection enabled, but do not want to have to select a column prior to right-click.
Any help would be appreciated!
You can walk up the visual tree looking for a ColumnDataPoint.
Here's a sample graph:
<Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown">
<Grid.Resources>
<PointCollection x:Key="sampleData">
<Point X="1" Y="6"/>
<Point X="2" Y="4"/>
<Point X="3" Y="8"/>
</PointCollection>
</Grid.Resources>
<chartingToolkit:Chart Title="Chart Title">
<chartingToolkit:ColumnSeries Name="chart1" Title="Column Series" ItemsSource="{StaticResource sampleData}" IndependentValueBinding="{Binding X}" DependentValueBinding="{Binding Y}"/>
</chartingToolkit:Chart>
</Grid>
and with this code-behind:
private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var element = Mouse.DirectlyOver as DependencyObject;
while (element != null && !(element is ColumnDataPoint))
{
element = VisualTreeHelper.GetParent(element);
}
if (element != null)
{
var columnDataPoint = element as ColumnDataPoint;
Debug.WriteLine("X = " + columnDataPoint.IndependentValue);
Debug.WriteLine("Y = " + columnDataPoint.DependentValue);
}
}
the X and Y values for the item the mouse is over will be printed out when the left mouse button is clicked.
Here is the example code that works.
MainWindow.xaml:
<Window x:Class="ColumnSeriesApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
xmlns:local="clr-namespace:ColumnSeriesApp"
Title="Pet Data" Height="350" Width="525">
MainWindow.xaml.cs:
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 ColumnSeriesApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public PetData m_PetData;
public MainWindow()
{
m_PetData = new PetData();
DataContext = m_PetData;
InitializeComponent();
}
private void m_colserHistogram_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
// Figure out what column we are on and display a popup menu based on the information.
IInputElement ieMouseOver = e.MouseDevice.DirectlyOver;
Rectangle rMouseOver = (Rectangle)ieMouseOver;
string strMouseOverContext= rMouseOver.DataContext.ToString();
string strMouseOverKey= "";
foreach (var vKvP in m_PetData)
{
if (1 == strMouseOverContext.IndexOf(vKvP.Key))
strMouseOverKey = vKvP.Key;
}
if (!String.IsNullOrEmpty(strMouseOverKey))
MessageBox.Show("The X value is " + strMouseOverKey);
}
}
public class PetData : Dictionary<string, int>
{
public PetData()
{
Add("SallyBeagle", 7);
Add("Cujo", 10);
Add("DobyDeedle", 11);
Add("Caramel", 6);
Add("Boo", 6);
}
}
}
It seems to work pretty well. If Rick had not come back with an idea, I would probably have quit looking for awhile - thanks for the motivation!
Now - is this solution all MVVM and whatnot? It still feels a little like a hack....
I am creating a Paint application with C#, WPF, and Visual Studio 4. Just like MS Paint, the user can draw lines and shapes.
How do I fill a user drawn irregular shape with color? Is there a library for doing something like this? Detecting a closed loop that is created by lines seem like an impractical approach. I can imagine all kinds of "leaks" because of one pixel gap.
Thanks.
Here's a simple SSCCE.
Edit: It might not be precisely what you wanted, now re-reading the question, but perhaps it can give you an idea of how to structure your filling tools. As for pixel-perfection, even MS Paint requires a complete closed drawing, where as other libraries might use degrees of antialias matching to avoid "leaks".
XAML:
<Window x:Class="FreeformDrawing.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
MouseLeftButtonDown="Window_MouseLeftButtonDown"
MouseLeftButtonUp="Window_MouseLeftButtonUp"
MouseMove="Window_MouseMove"
Title="MainWindow" Height="400" Width="400">
<Grid>
<Canvas x:Name="DrawingCanvas" />
</Grid>
</Window>
C#:
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 FreeformDrawing
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Polygon polygon;
private bool isDrawing = false;
public MainWindow()
{
InitializeComponent();
}
public void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (!isDrawing)
{
isDrawing = true;
polygon = new Polygon()
{
Stroke = Brushes.Black,
StrokeThickness = 1,
StrokeMiterLimit = 1,
StrokeLineJoin = PenLineJoin.Round,
StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round
};
AddPoint(e.GetPosition(DrawingCanvas));
DrawingCanvas.Children.Add(polygon);
}
}
public void Window_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
isDrawing = false;
if (polygon != null)
{
polygon.Points.Add(polygon.Points.First());
polygon.Fill = Brushes.Yellow;
}
}
public void Window_MouseMove(object sender, MouseEventArgs e)
{
if (isDrawing)
{
AddPoint(e.GetPosition(DrawingCanvas));
}
}
private void AddPoint(Point value)
{
if (value.X < (DrawingCanvas.ActualWidth - 1)
&& value.Y < (DrawingCanvas.ActualHeight - 1))
{
polygon.Points.Add(value);
}
}
}
}
Flood Fill Algoritms look like the way to go -
http://en.wikipedia.org/wiki/Flood_fill