I'm trying to place an object on a random location depending on the window size.
LayoutRoot is the name of the grid it's placed in.
//Give Dot a random position
int left = random.Next(LayoutRoot.MinWidth, LayoutRoot.MaxWidth);
int top = random.Next(-900, 900);
Dot.Margin = new Thickness(left, top, 0, 0);
Error on LayoutRoot.MinWidth & MaxWidth: Cannot convert double to int
tried
//Give Dot a random position
double left = random.NextDouble(LayoutRoot.MinWidth, LayoutRoot.MaxWidth);
double top = random.Next(-900, 900);
Dot.Margin = new Thickness(left, top, 0, 0);
Error on NextDouble: Method NextDouble takes 2 arguments
Ok going to up this one up a bit. Modified your code. This code doesn't assume any predefined height or width, it gets that based on the current size of the LayoutRoot. It also offsets the Dot size so it doesn't fall off the screen.
MainWindow.xaml
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="LayoutRoot">
<Ellipse Width="2" Height="2" Fill="Black" x:Name="Dot"></Ellipse>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow
{
private static Random random = new Random();
public MainWindow()
{
InitializeComponent();
// Don't move dot until the window is loaded, not necessary but generally you don't want to hold up the window from displaying.
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
// The farthest left the dot can be
double minLeft = 0;
// The farthest right the dot can be without it going off the screen
double maxLeft = LayoutRoot.ActualWidth - Dot.Width;
// The farthest up the dot can be
double minTop = 0;
// The farthest down the dot can be without it going off the screen
double maxTop = LayoutRoot.ActualHeight - Dot.Height;
double left = RandomBetween(minLeft, maxLeft);
double top = RandomBetween(minTop, maxTop);
Dot.Margin = new Thickness(left, top, 0, 0);
}
private double RandomBetween(double min, double max)
{
return random.NextDouble() * (max - min) + min;
}
}
}
this might work.
double mWidth = LayoutRoot.MinWidth;
double mxWidth = LayoutRoot.MaxWidth;
double left = random.NextDouble(mWidth, mxWidth);
double top = random.Next(-900, 900);
Dot.Margin = new Thickness(left, top, 0, 0);
}
So after looking at the answer given by Kelly I had a similar issue where the object would not display after loading. My goal was to have the welcome screen of an application randomly move every 10 seconds. So following his design there was an issue where because the grid did not have an actual length due to the lack of column/row definitions we have to force the app to recalculate size at every attempt to randomize.
Here is the XAML I used:
<Grid x:Name="LayoutRoot">
<TextBlock x:Name="WelcomeTextBlock"
Height="200"
Style="{StaticResource WelcomeTextBlock}">
Welcome!
</TextBlock>
<Ellipse Width="2" Height="2" x:Name="Dot"/>
</Grid>
And more importantly the actual code-behind:
public partial class WelcomePage : Page
{
private static Random Rnd = new Random();
private static DispatcherTimer _timer;
public WelcomePage()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnClosing;
}
private void OnLoaded(object sender, RoutedEventArgs args)
{
this.LayoutRoot.Margin = GetNewMargin();
_timer = new DispatcherTimer {Interval = TimeSpan.FromSeconds(5)};
_timer.Tick += MoveWelcome;
_timer.Start();
}
private void MoveWelcome(object sender, EventArgs e)
{
this.LayoutRoot.Margin = GetNewMargin();
}
private Thickness GetNewMargin()
{
this.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
this.Arrange(new Rect(0, 0, this.DesiredSize.Width, this.DesiredSize.Height));
var maxLeft = this.LayoutRoot.ActualWidth - Dot.Width;
var maxTop = LayoutRoot.ActualHeight - Dot.Height;
var left = RandomBetween(0, maxLeft);
var top = RandomBetween(0, maxTop);
return new Thickness(left, top, 0 ,0);
}
private static void OnClosing(object sender, RoutedEventArgs args)
{
_timer.Stop();
}
private static double RandomBetween(double min, double max) => Rnd.NextDouble() * (max - min) + max;
Besides the timer functionality the major change here is that when the margin thickness is being calculated we force the window, or in my case page to update the measurements of the screen and arrange the items correctly.
Related
I'm currently porting a perl-tk-application to c# wpf. The application supplies a graphical interface with different timelines for different systems. The timelines consist of rectangles - each one representing a special event - that can be clicked for obtaining deeper information about the event. The rectangles have different sizes depending on the length of the event - so their sizes (width) are different and not predictable.
All I need now is the possibility to bind an event to each of the rectangles that - at least - lets me track back which rectangle was clicked. In Perl it was as easy as this:
$rectangle = $Canvas->create_rectangle( $x1, $y1 ,$x2 ,oy2 , -outline => "red", -fill => "red");
$Canvas->bind($rectangle, "<1>", sub {DoAction[$number]});
That means you can just put the event-binding after the element that needs to be clickable.
I've wasted the whole weekend in searching for a solution to do this in c# wpf... Important to know - I'm an absolut newbie in c#.
My code so far: I generated 10 rectangles via array. I want to pass the number of the rectangle-array to the ClickEvent. In the following example code the ClickEvent always prints out the highest index. I assume, there exists only one Event and I would need to generate a array of events...? What's the solution for this? In short words: Which rectangle (number) was clicked?
private void ClickEvent (object sender, EventArgs e, int i) {
var time = DateTime.Now;
string name = ((Shape)sender).Name;
Console.WriteLine("Rectangle click at " + time + " from " + name + " Rect. Nr." + i);
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
int NumObjects = 10;
Rectangle[] RectangleArray = new Rectangle[NumObjects];
for (int i = 0; i < NumObjects - 1; i++) {
RectangleArray[i] = new Rectangle();
RectangleArray[i].Width = 50;
RectangleArray[i].Height = 50;
RectangleArray[i].Fill = Brushes.Red;
Canvas.SetTop(RectangleArray[i], i * 50);
Canvas.SetLeft(RectangleArray[i], i * 50);
RectangleArray[i].MouseLeftButtonDown += (sender2, e2) => ClickEvent(sender2, e2, i);
Canvas1.Children.Add(RectangleArray[i]);
}
}
XAML:
<Window x:Class="WpfRectangleEvent.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:WpfRectangleEvent"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
Loaded="Window_Loaded">
<Grid>
<Canvas HorizontalAlignment="Left" Height="666" Margin="-17,-20,0,0"
VerticalAlignment="Top" Width="517"
Name="Canvas1" Grid.ColumnSpan="2">
</Canvas>
</Grid>
In the event handler method the sender object is the Rectangle which invokes the event.
So if you store the rectangles, you can find out the index of that rectangle.
OK! Got it, this works:
private void ClickEvent(object sender, EventArgs e, int i) {
var time = DateTime.Now;
Console.WriteLine("Rectangle click at " + time + " from Rect. Nr." + i);
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
int NumObjects = 10;
Rectangle[] RectangleArray = new Rectangle[NumObjects];
for (int i = 0; i < NumObjects - 1; i++) {
int index = i;
RectangleArray[i] = new Rectangle();
RectangleArray[i].Width = 50;
RectangleArray[i].Height = 50;
RectangleArray[i].Fill = Brushes.Red;
Canvas.SetTop(RectangleArray[i], i * 50);
Canvas.SetLeft(RectangleArray[i], i * 50);
RectangleArray[i].MouseLeftButtonDown += (sender2, e2) => ClickEvent(sender2, e2, index);
Canvas1.Children.Add(RectangleArray[i]);
}
}
I'm trying to add some ellipces with random positions into my canvas, but i can see them on my canvas. Progmab is compilling quite saccesfull. Code:
for (int i = 0; i < FirefliesCount; ++i)
{
Firefly CurrentFirefly = new Firefly();
CurrentFirefly.Speed = Randomer.Next(1, 3);
CurrentFirefly.Body = new Ellipse();
CurrentFirefly.Body.Margin = new Thickness(Randomer.Next(10, (int)MainCanvas.Width - 10),
Randomer.Next(10, (int)MainCanvas.Height - 10),
0, 0);
CurrentFirefly.Body.Fill = Brushes.Black;
CurrentFirefly.Body.Height = MainCanvas.Height / 4;
CurrentFirefly.Body.Width = 1.5 * CurrentFirefly.Body.Height;
MainCanvas.Children.Add(CurrentFirefly.Body);
}
And Fireflie class:
class Firefly
{
public Ellipse Body { get; set; }
public int Speed { get; set; }
}
Probably you did not set the Width and Height properties of your MainCanvas; then they have the value NaN and therefore you will not see the ellipses.
My suggestion is to use ActualWidth and ActualHeight instead and to delay the adding of the ellipses until the canvas is loaded. Here is an example:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainCanvas.Loaded += MainCanvas_Loaded;
}
void MainCanvas_Loaded(object sender, RoutedEventArgs e)
{
Init();
}
private void Init()
{
const int FirefliesCount = 100;
Random Randomer = new Random();
for (int i = 0; i < FirefliesCount; ++i)
{
Firefly CurrentFirefly = new Firefly();
CurrentFirefly.Speed = Randomer.Next(1, 3);
CurrentFirefly.Body = new Ellipse();
CurrentFirefly.Body.Margin = new Thickness(Randomer.Next(10, (int)MainCanvas.ActualWidth - 10),
Randomer.Next(10, (int)MainCanvas.ActualHeight - 10),
0, 0);
CurrentFirefly.Body.Fill = Brushes.Black;
CurrentFirefly.Body.Height = MainCanvas.ActualHeight / 4;
CurrentFirefly.Body.Width = 1.5 * CurrentFirefly.Body.Height;
MainCanvas.Children.Add(CurrentFirefly.Body);
}
}
}
The corresponding xaml file looks like this:
<Window x:Class="WpfApplication7.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="MainCanvas"/>
</Window>
I am relatively new to C# and I'm trying to create a window with a grid of a dynamic amount of same-sized squares. The squares are then to have their color changed by a process.
I'm currently struggling to generate the grid of squares. Whenever I run the application it seems to be going crazy on resources and I'm not sure why.
The code I am using follows:
private void Window_Loaded(object sender, RoutedEventArgs e) {
//create a blue brush
SolidColorBrush vBrush = new SolidColorBrush(Colors.Blue);
//set the columns and rows to 100
cellularGrid.Columns = mCellAutomaton.GAME_COLUMNS;
cellularGrid.Rows = mCellAutomaton.GAME_ROWS;
//change the background of the cellular grid to yellow
cellularGrid.Background = Brushes.Yellow;
//create 100*100 blue rectangles to fill the cellular grid
for (int i = 0; i < mCellAutomaton.GAME_COLUMNS; i++) {
for (int j = 0; j < mCellAutomaton.GAME_ROWS; j++) {
Rectangle vRectangle = new Rectangle();
vRectangle.Width = 10;
vRectangle.Height = 10;
vRectangle.Fill = vBrush;
cellularGrid.Children.Add(vRectangle);
}
}
}
Is this even the approach I want to take if I want a modifiable grid of squares?
Thanks for any help,
Jason
this seems to perform pretty fast
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="350" Name="UI">
<Grid Name="Test">
<UniformGrid Name="UniGrid" />
</Grid>
</Window>
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
AddRows(new Size(10, 10));
}
private void AddRows(Size recSize)
{
UniGrid.Columns = (int)(UniGrid.ActualWidth / recSize.Width);
UniGrid.Rows = (int)(UniGrid.ActualHeight / recSize.Height);
for (int i = 0; i < UniGrid.Columns * UniGrid.Rows; i++)
{
UniGrid.Children.Add(new Rectangle { Fill = new SolidColorBrush(Colors.Yellow), Margin = new Thickness(1) });
}
}
}
I am trying to write a WPF application where you can draw circles on a window by double clicking it. So far I have this code:
public class ShapeAdorner : Adorner
{
private readonly Ellipse _circle;
public ShapeAdorner(UIElement adornedElement, Point circleCenter)
: base(adornedElement)
{
_circle = new Ellipse
{
Width = 10,
Height = 10,
Stroke = Brushes.Black,
StrokeThickness = 1.5
};
_circle.Margin =
new Thickness(left: circleCenter.X, top: circleCenter.Y, right: 0, bottom: 0);
base.AddVisualChild(_circle);
}
protected override Size ArrangeOverride(Size finalSize)
{
_circle.Arrange(new Rect(finalSize));
return finalSize;
}
protected override Size MeasureOverride(Size constraint)
{
_circle.Measure(constraint);
return constraint;
}
protected override Visual GetVisualChild(int index)
{
return _circle;
}
protected override int VisualChildrenCount
{
get { return 1; }
}
}
Here's the client code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(myLabel);
adornerLayer.Add(new ShapeAdorner(adornedElement: myLabel, circleCenter: e.GetPosition(myLabel)));
}
}
The circles are supposed to be centered at the point where you double click the window; however, the circles drawn by the code above are centered below and to the right of "the double click point". How can this be fixed?
EDIT: myLabel has Height=350 and Width=525. Let's say that I double click the point (X,Y); then the circle gets plotted at ((350+X)/2,(525+Y)/2).
EDIT 2: Just for completeness, here's the .xaml file:
<Window x:Class="Adorners.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Adorners project" Height="350" Width="525" MouseDoubleClick="Window_MouseDoubleClick">
<Grid>
<Label Name="myLabel" Content="my label" Background="Red"></Label>
</Grid>
</Window>
Where you set the margin you have to subtract the radius from the top and left properties to offset the circle.
You'll need to offset by half the width/height of the circle. Hard-coded here to make it easy to follow:
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(myLabel);
var point = e.GetPosition(myLabel);
point.X -= 5;
point.Y -= 5;
adornerLayer.Add(new ShapeAdorner(myLabel, point));
You need to take the witdth and height into conserderation when you're setting the margin. the top should be the centerposition minus half of the height, and the same for the left:
new Thickness(
left: circleCenter.X + (_circle.Width/2), //farther to the right
top: circleCenter.Y - (_circle.Height/2), //higher up
right: 0, bottom: 0);
The previous answers are correct. However, the main problem was that I had omitted these two lines:
_circle.HorizontalAlignment = HorizontalAlignment.Left;
_circle.VerticalAlignment = VerticalAlignment.Top;
The default value Stretch caused a huge offset error.
Using the following XAML:
<Grid x:Name="grid" Background="LightBlue" ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Viewbox x:Name="imgViewbox" >
<InkCanvas Grid.Row="0" Name="inkCanvas" Background="Red" >
<Image Source="Images/pic.png" HorizontalAlignment="Left" x:Name="imgObject" VerticalAlignment="Top" />
<Label>Testing</Label>
</InkCanvas>
</Viewbox>
</Grid>
I am trying to rotate around the center of the image and also use the wheel mouse to zoom. I have set up this transform group and event:
public MainWindow() {
InitializeComponent();
DataContext = new MainWindowViewModel();
transformGroup = new TransformGroup();
scaleTransform = new ScaleTransform();
rotateTransform = new RotateTransform();
translateTransform = new TranslateTransform();
transformGroup.Children.Add(rotateTransform);
transformGroup.Children.Add(scaleTransform);
transformGroup.Children.Add(translateTransform);
imgViewbox.RenderTransform = transformGroup;
imgViewbox.MouseWheel += ImageViewboxMouseWheel;
}
Rotate is simple:
void Rotate(object sender, RoutedEventArgs e) {
//imgViewbox.RenderTransformOrigin = new Point(0.5,0.5);
rotateTransform.Angle += 90;
}
but zoom is doing all sorts of weird stuff jumping around the screen. The code for zoom is here:
void ImageViewboxMouseWheel(object sender, MouseWheelEventArgs e) {
//imgViewbox.RenderTransformOrigin = new Point(0, 0);
double zoomFactor = DefaultZoomFactor;
if (e.Delta <= 0) zoomFactor = 1.0 / DefaultZoomFactor;
// DoZoom requires both the logical and physical location of the mouse pointer
var physicalPoint = e.GetPosition(imgViewbox);
if (transformGroup.Inverse != null) {
DoZoom(zoomFactor, transformGroup.Inverse.Transform(physicalPoint), physicalPoint);
}
else {
throw new ArgumentException("Missing Inverse");
}
//Set the center point of the ScaleTransform object to the cursor location.
scaleTransform.CenterX = e.GetPosition(imgViewbox).X;
scaleTransform.CenterY = e.GetPosition(imgViewbox).Y;
Debug.WriteLine(string.Format("IVMW Center {0},{1}", scaleTransform.CenterX, scaleTransform.CenterY));
}
public void DoZoom(double deltaZoom, Point mousePosition, Point physicalPosition) {
double currentZoom = scaleTransform.ScaleX;
currentZoom *= deltaZoom;
translateTransform.X = -1*(mousePosition.X*currentZoom - physicalPosition.X);
translateTransform.Y = -1*(mousePosition.X*currentZoom - physicalPosition.Y);
scaleTransform.ScaleX = currentZoom;
scaleTransform.ScaleY = currentZoom;
}
I have removed as much as I can, animations and such. Hopefully leaving only the key parts. I believe that the major problem is the scaleTransform.Center[X|Y] as the numbers that are being returned are all over the quadrant even when I try to click exactly in the same location. The RenderTransformOrigin doesn't seem to make any difference with the Center position but I am aware that I need it to rotate around center.
What am I doing wrong?
You need to offset the jump you get from changing the ScaleTranform's CenterX/Y in the TranslateTransform, here is a snippet from a pan & zoom control i wrote:
private void This_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (IsZoomEnabled)
{
Point cursorPos = e.GetPosition(this);
Point newCenter = _scaleT.Inverse.Transform(_translateT.Inverse.Transform(cursorPos));
Point oldCenter = new Point(_scaleT.CenterX, _scaleT.CenterY);
Vector oldToNewCenter = newCenter - oldCenter;
_scaleT.CenterX = newCenter.X;
_scaleT.CenterY = newCenter.Y;
_translateT.X += oldToNewCenter.X * (_scaleT.ScaleX - 1.0);
_translateT.Y += oldToNewCenter.Y * (_scaleT.ScaleY - 1.0);
...
Hopefully you can adapt this to your code. Where the new center is calculated you might need to take your RotateTransform into account.