Area Calculation of CombinedGeometry parts - wpf

I need to calculate some areas based on Geometry intersection.
In my example I have the following Geometries:
Left RectangleGeometry
Right RectangleGeometry
EllipseGeometry
The Ellipse is in the middle of the Rectangles and I want two get the following data:
Area of the intersection between the Ellipse and left rectangle
Area of the intersection between the Ellipse and the right rectangle
Total Area of the Ellipse.
The issue is that the total area of the ellipse, EllipseGeometry.GetArea(), and the "LeftEllipseGeometry".GetArea() + "RightEllipseGeometry".GetArea() are different.
The sum of intersections areas have to be the same as the ellipe Area.
I made an example where you can test and see the problem.
MainWindow.xaml.cs
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
//LEFT
rectLeft = new RectangleGeometry();
rectLeft.Rect = new Rect(new Point(75, 100), new Point(700, 600));
Path pathRectLeft = new Path();
pathRectLeft.Stroke = Brushes.Red;
pathRectLeft.Data = rectLeft;
grdMain.Children.Add(pathRectLeft);
//RIGHT
rectRight = new RectangleGeometry();
rectRight.Rect = new Rect(new Point(700, 100), new Point(1300, 600));
Path pathRectRight = new Path();
pathRectRight.Stroke = Brushes.Green;
pathRectRight.Data = rectRight;
grdMain.Children.Add(pathRectRight);
//ELLIPSE
ellipseGeo = new EllipseGeometry();
ellipseGeo.RadiusX = 200;
ellipseGeo.RadiusY = 200;
ellipseGeo.Center = new Point(700, 350);
Path ellipsePath = new Path();
ellipsePath.Stroke = Brushes.Blue;
ellipsePath.Data = ellipseGeo;
grdMain.Children.Add(ellipsePath);
lblEllipseArea.Content = String.Concat("Area Ellipse = ", ellipseGeo.GetArea());
}
private void Button_Click(object sender, RoutedEventArgs e)
{
CombinedGeometry cgLeft = new CombinedGeometry();
cgLeft.Geometry1 = rectLeft;
cgLeft.Geometry2 = ellipseGeo;
cgLeft.GeometryCombineMode = GeometryCombineMode.Intersect;
Path cgLeftPath = new Path();
cgLeftPath.Stroke = Brushes.Yellow;
cgLeftPath.Data = cgLeft;
grdMain.Children.Add(cgLeftPath);
lblEllipseAreaLeft.Content = String.Concat("Area Left Ellipse = ", cgLeft.GetArea());
CombinedGeometry cgRight = new CombinedGeometry();
cgRight.Geometry1 = rectRight;
cgRight.Geometry2 = ellipseGeo;
cgRight.GeometryCombineMode = GeometryCombineMode.Intersect;
Path cgRightPath = new Path();
cgRightPath.Stroke = Brushes.White;
cgRightPath.Data = cgRight;
grdMain.Children.Add(cgRightPath);
lblEllipseAreaRight.Content = String.Concat("Area Right Ellipse = ", cgRight.GetArea());
lblEllipseTotal.Content = String.Concat("Area Ellipse Total = ", cgLeft.GetArea() + cgRight.GetArea());
}
MainWindow.xaml
<Grid>
<StackPanel Orientation="Vertical" Background="Black">
<Grid Background="Black" Height="700" Name="grdMain">
</Grid>
<Grid Background="Black" Height="150">
<StackPanel Orientation="Vertical">
<Button Height="30" Width="70" Click="Button_Click">Click Me!!!</Button>
<StackPanel Orientation="Horizontal">
<Label Foreground="White" Name="lblEllipseArea"></Label>
<Label Foreground="White" Name="lblEllipseArea2" Margin="20 0 0 0"></Label>
<Label Foreground="White" Name="lblEllipseAreaRight" Margin="20 0 0 0"></Label>
<Label Foreground="White" Name="lblEllipseAreaRight2" Margin="20 0 0 0"></Label>
<Label Foreground="White" Name="lblEllipseAreaLeft" Margin="20 0 0 0"></Label>
<Label Foreground="White" Name="lblEllipseAreaLeft2" Margin="20 0 0 0"></Label>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Foreground="White" Name="lblEllipseTotal" Margin="20 0 0 0"></Label>
<Label Foreground="White" Name="lblEllipseTotal2" Margin="20 0 0 0"></Label>
</StackPanel>
</StackPanel>
</Grid>
</StackPanel>
</Grid>

I think you might never get the "exact" areas from the CombinedGeometry. As expected, WPF does not use the "ideal" method to calculate this values. From MSDN: "Some Geometry methods (such as GetArea) produce or use a polygonal approximation of the geometry".
Check MSDN

Related

Databinding Progress Bar Not Binding

I searched a lot for the solution to this, but can't seem to figure it out. (Just saying, this will be a long post)
Preface: This is NOT MVVM
Problem: I have a progress bar. I am currently trying to bind a public double to it from within the xaml's .cs file. However, whenever I use value's bind function, it never works, resulting in iNotifyPropertyChanged not working, etc. etc....
The variable it is bound to serves the purpose of updating the progress bar value. The way that is completed is by setting the value in a separate for loop in another method/class. The value is set by a simple equation. 100 divided by the length of a list, multiplied by the for loops counter. This gives us a real number that can be put into the progress bar value. (code below)
Binder.daWindow.progUpdate = ((100.00 / databaseTables.Length) * dbTIndex);
I know this can be completed by threading (or whatever), but I would appreciate answers specifically for how this might be done by binding (unless it's impossible, then forget what I just said).
XAML:
<Window x:Name="DateWindow1" x:Class="WpfApp1.DateWindow"
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:WpfApp1"
xmlns:Main="using:DateWindow"
mc:Ignorable="d"
Title="Change Dates" Height="232.667" Width="462" Background="#FF2B2B2B" ResizeMode="NoResize">
<Grid>
<TextBox x:Name="DayChangeTextBox" HorizontalAlignment="Left" Height="30" Margin="176,45,0,0" TextWrapping="Wrap" Text="##" VerticalAlignment="Top" Width="58" FontSize="14" TextAlignment="Center" AcceptsReturn="False" AcceptsTab="False" GotFocus="DayChangeTextBox_GotFocus" LostFocus="DayChangeTextBox_LostFocus" BorderThickness="1" BorderBrush="Black" OpacityMask="Black" Background="#FFFDFDFD"/>
<Label Content="Days" HorizontalAlignment="Left" Margin="239,45,0,0" VerticalAlignment="Top" Width="42" FontSize="14" FontFamily="Microsoft YaHei UI" Height="30" Foreground="White"/>
<Label Content="Move Dates Forward:" HorizontalAlignment="Left" Margin="23,45,0,0" VerticalAlignment="Top" Width="144" FontSize="14" FontFamily="Microsoft JhengHei UI Light" Height="30" Foreground="White"/>
<Button x:Name="ChangeDatesButton" Content="Change Dates" HorizontalAlignment="Left" Margin="303,45,0,0" VerticalAlignment="Top" Width="143" Height="30" BorderThickness="1,1,2,3" BorderBrush="#FF474747" Click="ChangeDatesButton_Click" FontFamily="Microsoft YaHei UI" Background="#FFE6E6E6"/>
<Label x:Name="serverLabel" Content="Label" HorizontalAlignment="Left" Margin="10,73,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.5,0.5" Width="5" Height="15" Visibility="Hidden"/>
<Label x:Name="databaseLabel" Content="Label" HorizontalAlignment="Left" Margin="20,73,0,0" VerticalAlignment="Top" Width="3" Visibility="Hidden" Height="15"/>
<Button x:Name="RevertToDefaultButton" Content="Revert to Backup" Margin="23,164,0,0" VerticalAlignment="Top" Height="30" Background="#FFE6E6E6" BorderBrush="#FF474747" BorderThickness="1,1,2,3" Click="RevertToDefaultButton_Click" FontFamily="Microsoft JhengHei UI" HorizontalAlignment="Left" Width="143"/>
<Button x:Name="OverwriteDefaultButton" Content="Backup Database" Margin="23,128,0,0" VerticalAlignment="Top" Height="30" BorderBrush="#FF474747" BorderThickness="1,1,2,3" Click="OverwriteDefaultButton_Click" FontFamily="Microsoft JhengHei UI" HorizontalAlignment="Left" Width="143" Background="#FFE6E6E6" />
<Button x:Name="CloseButton" Content="Close" Margin="0,164,10,0" VerticalAlignment="Top" Height="30" BorderBrush="#FF474747" BorderThickness="1,1,2,3" FontFamily="Microsoft JhengHei UI" Width="60" Click="CloseButton_Click" HorizontalAlignment="Right" Background="#FFE6E6E6" />
<Label x:Name="RevertBackupLabel" Content="Backup or Revert Database:" HorizontalAlignment="Left" Margin="12,93,0,0" VerticalAlignment="Top" Foreground="White" FontFamily="Microsoft JhengHei UI" FontWeight="Bold" FontSize="16"/>
<Label x:Name="DatesLabel" Content="Change Dates in Database:" Margin="12,10,0,0" Foreground="White" FontFamily="Microsoft JhengHei UI" FontWeight="Bold" FontSize="16" HorizontalAlignment="Left" Width="216" Height="30" VerticalAlignment="Top"/>
<ProgressBar HorizontalAlignment="Left" Height="13" Margin="303,80,0,0" VerticalAlignment="Top" Width="143" Name="pbStatus" Value="{Binding local.progUpdate, UpdateSourceTrigger=PropertyChanged}" Maximum="{Binding mx}" Minimum="{Binding mn}" LargeChange="0.01"/>
</Grid>
XAML CS Portion:
public static class Binder
{
public static DateWindow daWindow;
}
public partial class DateWindow : Window, INotifyPropertyChanged
{
private double _progUpdate;
public double progUpdate
{
get { return _progUpdate; }
set
{
_progUpdate = value;
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(progUpdate)));
}
}
}
public double mx = 100;
public double mn = 0;
public event PropertyChangedEventHandler PropertyChanged;
public DateWindow()
{
InitializeComponent();
Binder.daWindow = this;
ChangeDateInDatabase ChangeDate = new ChangeDateInDatabase();
progUpdate = 25; // This is there so we can test the variable without going through the whole program.
}
private void ChangeDatesButton_Click(object sender, RoutedEventArgs e)
{
ChangeDateInDatabase ChangeDate = new ChangeDateInDatabase();
var result = MessageBox.Show("Are you sure you would like to change dates for " + Globals.selectedDatabase + " from server " + Globals.main.ServerComboBox.Text + "?", "Change Dates?", MessageBoxButton.YesNo, MessageBoxImage.Exclamation);
if (Globals.main.ServerComboBox.Text != "" && Globals.selectedDatabase != "" && FindDatabaseAndServer.Connector(Globals.main.ServerComboBox.Text, Globals.selectedDatabase) && result == MessageBoxResult.Yes)
{
try
{
long.Parse(DayChangeTextBox.Text);
ChangeDate.ChangeDates(this);
//ChangeDateInDatabase.ChangeDates(this);
}
catch
{
MessageBox.Show($"Invalid number {DayChangeTextBox.Text}, please enter a valid number.", "ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
(CLICK function serves the purpose of calling a method for the program. Inside the method, its main loop, which determines the methods completion, contains the variable desired to be bound with a defining equation for its value)
Method Containing Said Loop:
for (int dbTIndex = 0; dbTIndex < databaseTables.Length; dbTIndex++)
{
string[] dateColumns = new string[0];
string columnSelectQuery = "SELECT DATA_TYPE, COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '" + databaseTables[dbTIndex].ToString() + "' AND COLUMN_NAME LIKE '_%'";
using (SqlConnection connection2 = new SqlConnection(connectionString))
{
SqlCommand command2 = new SqlCommand(columnSelectQuery, connection2);
connection2.Open();
SqlDataReader reader2 = command2.ExecuteReader();
int y = 0;
while (reader2.Read())
{
if (reader2[0].ToString() == "datetime")
{
Array.Resize(ref dateColumns, dateColumns.Length + 1);
dateColumns[y] = reader2[1].ToString();
y++;
}
}
}
for (var dtColIndex = 0; dtColIndex < dateColumns.Length; dtColIndex++)
{
string addDatesQuery = "UPDATE " + databaseTables[dbTIndex] + " SET " + dateColumns[dtColIndex] + " = DATEADD(dd," + Convert.ToInt32(date.DayChangeTextBox.Text) + "," + dateColumns[dtColIndex] + ") WHERE " + dateColumns[dtColIndex] + " IS NOT NULL";
using (SqlConnection connection3 = new SqlConnection(connectionString))
{
SqlCommand command3 = new SqlCommand(addDatesQuery, connection3);
connection3.Open();
command3.ExecuteNonQuery();
}
}
//vvvvv VARIABLE THAT NEEDS TO BE BOUND vvvvvvv
Binder.daWindow.progUpdate = ((100.00 / databaseTables.Length) * dbTIndex);
}
(There is obviously more code to this part, which I can provide if needed)
Thanks in advance!
EDIT:
For the XAML, local.progUpdate is not the only thing I have tried. I've done path, progUpdate by itself, etc.
Your ProgressBar's first binding makes no sense: {Binding local.progUpdate, UpdateSourceTrigger=PropertyChanged}. You're not specifying a source, and there's no DataContext assigned anywhere, so the binding won't evaluate to anything because the path is meaningless. Because the property is on the window itself, you can just assign the window's data context to itself.
// DateWindow.xaml.cs
public DateWindow()
{
InitializeComponent();
DataContext = this;
...
}
<!-- DateWindow.xaml -->
<ProgressBar Value="{Binding progUpdate, Mode=OneWay}" ... />
Your other bindings will require mx and mn to be properties rather than fields.

unable to run video in WPF

I try to implement a video in WPF. The video is visible in Visual Studio (so I think the path is correct) but isn't visible (I also hear nothing) while I run the program. I have 1 MediaElement to implement the video and 3 Buttons (Play, Pause, Mute) below. The Build-Property of the video is "Resource".
XAML-Code:
<StackPanel HorizontalAlignment="Center" Width="340" Height="300" Margin="281,63,0,0" VerticalAlignment="Center">
<MediaElement Name="myMedia" Source="testvideo.wmv" LoadedBehavior="Manual" Width="320" Height="240" />
<StackPanel Orientation="Horizontal" Margin="0, 10, 0, 0">
<Button Content="Play" Margin="0, 0, 10, 0" Padding="5" Click="mediaPlay"/>
<Button Content="Pause" Margin="0, 0, 10, 0" Padding="5" Click="mediaPause"/>
<Button x:Name="muteButt" Content="Mute" Padding="5" Click="mediaMute"/>
</StackPanel>
</StackPanel>
Code Behind:
public MainWindow()
{
InitializeComponent();
myMedia.Volume = 100;
myMedia.Play();
myMedia.Position = new TimeSpan(0, 0, 5); // first frame 5 seconds
}
void mediaPlay(Object sender, EventArgs e)
{
myMedia.Play();
}
void mediaPause(Object sender, EventArgs e)
{
myMedia.Pause();
}
void mediaMute(Object sender, EventArgs e)
{
if (myMedia.Volume == 100)
{
myMedia.Volume = 0;
muteButt.Content = "Listen";
}
else
{
myMedia.Volume = 100;
muteButt.Content = "Mute";
}
}
I found my problem:
I had to set the property "Source" of the MediaElement in the code behind again. I don't know why, but now it works!

ContextMenu MenuItems adding continuously on Click event

on click event ,I have return adding of menu items to contextmenu.but on clicking more than once it keeps adding the menu items to the contextmenu. Here the below code am using for it.
<StackPanel Grid.Row="13" Orientation="Horizontal" FlowDirection="LeftToRight">
<Button Name="btnMobile" Content="Home" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0 0 20 0" Width="70"></Button>
<!--<extToolkit:DropDownButton x:Name="ddBtnMobile" VerticalAlignment="Top" Width="30" HorizontalAlignment="Right" Margin="0 0 30 0" Height="20"/>-->
<Button HorizontalAlignment="Left" Name="ddBtnMobile" Width="30" Click="OnddBtnMobileClick" Margin="0,0,0,5" >
<Button.Content>
<Path x:Name="btnArrow3" Margin="4" VerticalAlignment="Center" Width="10" Fill="#FF527DB5" Stretch="Uniform" HorizontalAlignment="Right" Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z "/>
</Button.Content>
<Button.ContextMenu>
<ContextMenu Name="cMenu">
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
code am using is below
private void OnddBtnMobileClick(object sender, RoutedEventArgs e)
{
mnItem = new MenuItem();
mnItem.Header ="B1";
cMenu.Items.Add(mnItem);
mnItem = new MenuItem();
mnItem.Header ="A1";
cMenu.Items.Add(mnItem);
mnItem = new MenuItem();
mnItem.Header="B 2";
cMenu.Items.Add(mnItem);
cMenu.AddHandler(MenuItem.ClickEvent, new RoutedEventHandler(OnMenuItemClick));
}
private void OnMenuItemClick(object sender, RoutedEventArgs e)
{
RoutedEventArgs args = e as RoutedEventArgs;
MenuItem item = args.OriginalSource as MenuItem;
string header = item.Header.ToString();
if (header == "Business")
{
btnMobile.Content = header;
}
else if (header == "Assistant")
{
btnMobile.Content = header;
}
}
how to solve my issue.. Is there any better way of writing the above logic. i.e., adding menu items of context menu at run time.
add a boolean data member which will check if the sub menu's were already added
private void OnddBtnMobileClick(object sender, RoutedEventArgs e)
{
if(alreadyAdded == true)
return;
alreadyAdded = true;
mnItem = new MenuItem();
mnItem.Header ="B1";
cMenu.Items.Add(mnItem);
mnItem = new MenuItem();
mnItem.Header ="A1";
cMenu.Items.Add(mnItem);
mnItem = new MenuItem();
mnItem.Header="B 2";
cMenu.Items.Add(mnItem);
cMenu.AddHandler(MenuItem.ClickEvent, new RoutedEventHandler(OnMenuItemClick));
}
Add the following code in the button click event's starting.
cMenu = new cMenu();
Thats you need to create a new instance.
Thanks,

WPF DataGrid Zoom to mouse position

I have a DataGrid that LayoutTransform is Binded to a Slider like that:
<DataGrid.LayoutTransform>
<ScaleTransform
ScaleX="{Binding ElementName=MySlider, Path=Value}"
ScaleY="{Binding ElementName=MySlider, Path=Value}" />
</DataGrid.LayoutTransform>
</DataGrid>
<Slider x:Name="MySlider"
Minimum="0.3"
Maximum="2.0"
SmallChange="0.1"
LargeChange="0.1"
Value="1.0"
IsSnapToTickEnabled="True"
TickFrequency="0.1"
TickPlacement="TopLeft"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Width="200"
Margin="0,0,61,0" />
<TextBlock Name="Lstate"
Text="{Binding ElementName=MySlider, Path=Value, StringFormat={}{0:P0}}"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Width="50" Height="20"
Margin="0,0,0,1" />
Now, in the Code I have the PreviewMouseWheel event with the following Code:
bool handle = (Keyboard.Modifiers & ModifierKeys.Control) > 0;
if (!handle)
return;
double value;
if (e.Delta > 0)
value = 0.1;
else
value = -0.1;
MySlider.Value += value;
And my question is: How to scroll to the actual Mouse Position like AutoCad or some other programs?
Thanks
Sorry for my bad english...
I have a very very good solution now:
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard"
EnableColumnVirtualization="False"
EnableRowVirtualization="True"
ScrollViewer.CanContentScroll="True"
private void Data_OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
// Scroll to Zoom
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{
// Prevent scroll
e.Handled = true;
var scrollview = FindVisualChild<ScrollViewer>(Data);
if (scrollview != null)
{
// The "+20" are there to account for the scrollbars... i think. Not perfectly accurate.
var relativeMiddle = new Point((Data.ActualWidth + 20) / 2 + (Mouse.GetPosition(Data).X - Data.ActualWidth / 2), (Data.ActualHeight + 20) / 2 + (Mouse.GetPosition(Data).Y - Data.ActualHeight / 2));
var oldLocation = Data.LayoutTransform.Transform(Data.PointFromScreen(relativeMiddle));
// Zoom
MySlider.Value += (e.Delta > 0) ? MySlider.TickFrequency : -MySlider.TickFrequency;
// Scroll
var newLocation = Data.LayoutTransform.Transform(Data.PointFromScreen(relativeMiddle));
// Calculate offset
var shift = newLocation - oldLocation;
if (scrollview.CanContentScroll)
{
// Scroll to the offset (Item)
scrollview.ScrollToVerticalOffset(scrollview.VerticalOffset + shift.Y / scrollview.ViewportHeight);
}
else
{
// Device independent Pixels
scrollview.ScrollToVerticalOffset(scrollview.VerticalOffset + shift.Y);
}
// Device independent Pixels
scrollview.ScrollToHorizontalOffset(scrollview.HorizontalOffset + shift.X);
}
}
}
It zooms to the Mouse Position on the Datagrid with and without virtualization.

Scaling InkCanvas strokes to fit specific dimensions to crop out unused space

I am attempting to scale an InkCanvas's contents (Strokes) to fit a fixed page size for printing. I want to essentially crop out all of the surrounding whitespace from the InkCanvas, and scale up the Strokes to fit the page while maintaining aspect ratio.
In the handler below the markup, you can see that I am changing the dimensions of the grid that starts at 800x300, and I'm making it 425x550, half of the size of a printable page.
markup:
<Grid>
<Button Height="100" Width="100" HorizontalAlignment="Left" PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown_1" />
<Grid Width="1200" Height="1400" Background="Aquamarine" HorizontalAlignment="Right" VerticalAlignment="Top">
<Grid Background="Red" x:Name="grid" Width="800" Height="300">
<Viewbox x:Name="vb" Width="800" Height="300" Stretch="Fill" StretchDirection="Both">
<InkCanvas Width="500" Height="500" Background="Transparent" IsEnabled="True"/>
</Viewbox>
</Grid >
</Grid>
</Grid>
codebehind file:
bool b = true;
private void Button_PreviewMouseLeftButtonDown_1(object sender, MouseButtonEventArgs e)
{
if (b)
{
//I toyed with using Uniform, UniformToFill, and Fill
vb.Stretch = Stretch.Fill;
grid.Width = 425;
grid.Height = 550;
//scale viewbox down until it fits horizontally
var scaleX = grid.Width / vb.Width;
vb.Width *= scaleX;
vb.Height *= scaleX;
//if constraining it to the width made it larger than it needed to be vertically, scale it down
if (vb.Height > grid.Height)
{
var scaleY = grid.Height / vb.Height;
vb.Width *= scaleY;
vb.Height *= scaleY;
}
b = false;
}
else
{
//reset it back to what it was
vb.Stretch = Stretch.Fill;
grid.Width = 800;
grid.Height = 300;
vb.Width = grid.Width;
vb.Height = grid.Height;
b = true;
}

Resources