WPF: Stretch rectangle if aspect ratio > 16:9, scale otherwise - c#

I'm trying to put a rectangle in a grid in a window that will change size regularly. I'm not working with absolute values, but with ratios.
So, there are three states the rectangle could have relative to the window/grid:
The default aspect ratio for the window is 16:9. If the window has that size, the rectangle should fit into the window perfectly, filling the window;
If the window's width is bigger than that, the rectangle should stretch with it. (So if the window's aspect ratio > 16/9, the rectangle stretches its width, thus still filling the entire window);
If the window's height is bigger than the 16:9 ratio, the rectangle inside should (1) not stretch vertically, and (2) align to the bottom of the grid.
This image explains it a lot clearer
I'm looking for a solution that doesn't involve changing code other than XAML, (so nothing in the .cs file), unless there is no other way. I did try finding a solution with C# code though:
RectName_OnSizeChanged(object sender, SizeChangedEventArgs) {
RectName.MaxHeight = 9/16 * RectName.Width;
}
but it doesn't seem to be working. (So why that is, is my bonus question)

How about this:
<Grid Background="CornflowerBlue" SizeChanged="ParentSizeChanged">
<Rectangle x:Name="theRect" Fill="Blue" HorizontalAlignment="Left" VerticalAlignment="Bottom" />
</Grid>
And this:
private void ParentSizeChanged(object sender, SizeChangedEventArgs e)
{
var parent = sender as FrameworkElement;
if (parent == null)
return;
theRect.Width = parent.ActualWidth;
theRect.Height = Math.Min(parent.ActualHeight, parent.ActualWidth * 6 / 9);
}

Related

WinUI3: How to change the size of a rectangle at runtime

The Goal
I want to build an audio visualizer based on 1/3 octaves like this one in an WinUI3 app:
I already have the sampling part, I only need to deal with the rendering and visual part.
What Have I tried?
While looking at the WinUI3 Gallery App, I've found that you can modify rectangles in a nice way using either a translation or a scale modifier(and it's also animated):
<!-- Automatically animate changes to Scale -->
<Rectangle x:Name="rectangle" Width="50" Height="50" Scale="1,1,1" >
<Rectangle.ScaleTransition>
<Vector3Transition />
</Rectangle.ScaleTransition>
<Rectangle.TranslationTransition>
<Vector3Transition />
</Rectangle.TranslationTransition>
</Rectangle>
using System.Numerics;
private void button_Click(object sender, RoutedEventArgs e)
{
rectangle.Scale = new Vector3(1, 1, 1);
rectangle.Translation = new Vector3(1, 1, 1);
}
Firstly I tried to have 11 very long rectangles and move them upwards using the value. The problem with this approach is that the rectangles cannot be bigger than the container. You can have a default scale that actually overrides this rule but it lead to blurry corners.
So I've decided to try to scale the Y value of the rectangles by the respective value for each octave.
What I have
Right now I have 11 rectangles in a StackPanel named "rectangles", each rectangle looking like this
<Rectangle
Width="50"
Height="40"
Margin="7,10,7,0"
Fill="{ThemeResource SystemAccentColor}"
RadiusX="7"
RadiusY="3"
Scale="1,0,1">
<Rectangle.ScaleTransition>
<Vector3Transition />
</Rectangle.ScaleTransition>
<Rectangle.TranslationTransition>
<Vector3Transition />
</Rectangle.TranslationTransition>
</Rectangle>
And a timer that runs this code every 10ms:
private void FetchOctaves(object state)
{
DispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
() =>
{
if (!Audio.IsPlaying()) return;
var octaves = Audio.GetFrequencyData().ToList();
for (var i = 0; i < octaves.Count; i++)
{
// Map() normalizes a value from a source min and max to a target min and max
var yScale = Map(octaves[i], -60f, 20f, 1f, 12f);
rectangles.Children[i].Scale = new Vector3(1, yScale, 1);
}
}
);
}
And the result looks like this:
The Problem(s)
As you can figure, my solution is quite hardcoded and it leads to some problems:
1. I cannot find a maximum scale value in order to appeal to all window sizes.
Bigger scale values make my rectangles go out of the container's bounds.
One solution to this would be to have a property MaxScale that adapts whenever the size of the container changes(I already modify the rectangle's width whenever this happens). But this doesn't solve the next issue:
2. When met with big Y-scale values my rectangle's rounded corners start to look blurry.
This probably happens because of how the app handles scale(by zooming in).
What I want
I want to know if there's a way to resize the actual height of the rectangles at runtime.
This would solve both of my problems and would allow me to make it responsive to different window sizes.
If that is not possible, I would appreciate if you would point me towards another solution to achieve this goal using WinUI3.
Thanks in advance!

Buttons to appear around click position of mouse

I'm creating a simple WPF app in which one of the core features would be that wherever the user clicks on the grid inside the main window, a number of buttons should appear around the position of the click.
Now, I try to achieve this with only 1 button. I know that I have to capture the current position of the mouse and then modify the 4 arguments of the Margin of the button (left, top, right, bottom) by creating new instances of Thickness-es.
I managed to create new Thickness-es to the Margins, with the left and top argument set to the mouse X and Y cordinates respectively, but I don't know how to calculate or what to use as the right, and bottom arguments of the newly created Margins.
Here is the relevant function from the xaml.cs (the values in question are indicated as 0-s and grid is intented to refer to the grid):
private void Grid_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var mouseLocation = PointToScreen(Mouse.GetPosition(grid));
RandomButton.Margin = new Thickness(mouseLocation.X, mouseLocation.Y, 0, 0);
}
Here is the relevant part of the xaml:
<StackPanel>
<Button
Name="RandomButton"
Height="30"
Width="30"
Background="#FF130889"
Click="RandomButton_Click"
Content="RandomContent" />
</StackPanel>
It is also worth mentioning that when the button's HorizontalAlignment is set to Left and the VerticalAlignment is set to top, the button seem to do what I want with this setup, but only, when the windowsize is full.
I think I have to use the actual height of the window or the grid, but I don't know how. I know it is something simple, but I just started working with WPF, so I apreciate any kind of help!
As far as I understood the problem is in determining the click relative position.
In this case you can use Mouse.GetPosition method.
Here is the example with Canvas:
private void SetPos()
{
var relativePosition = Mouse.GetPosition(this.MainCanvas);
Canvas.SetLeft(this.btn1, relativePosition.X);
Canvas.SetTop(this.btn1, relativePosition.Y);
btn1.Visibility = Visibility.Visible;
}

How to draw a point over an image in Image(WPF), runtime with event

I have the following problem: I'm using Image component in WPF. I load my Image that is a map for me. I want to make a Button that if I click on, I will be allowed to click on my map and draw a point. That point should be "unique", so it should remember coordinates/description(I will store it in a database).
Coordinates should be read only from that image, not from the whole form.
I need OnMouseClick event after my point creation.
What should I use/read about to do that ?
Canvas allows you to place an object using left/top coordinates.
You need to place canvas exactly over the image with same dimensions as image.
To accomplish this, you should use a Grid, as it allows controls to stack over each other.
You can get the mouse click coordinates using e.GetPosition() in MouseLeftButtonDown event handler. As canvas is covering up image, so you will get coord according to image.
<Grid>
<Image x:Name="MapImg" Source="img/map.gif" Stretch="Fill" MouseLeftButtonDown="Image_MouseLeftButtonDown_1"/>
<Canvas x:Name="Cnv"/>
</Grid>
private void Image_MouseLeftButtonDown_1(object sender, MouseButtonEventArgs e)
{
Ellipse ellipse = new Ellipse();
ellipse.Fill = Brushes.Sienna;
ellipse.Width = 100;
ellipse.Height = 100 ;
ellipse.StrokeThickness = 2;
Cnv.Children.Add(ellipse);
Canvas.SetLeft(ellipse, e.GetPosition(MapImg).X);
Canvas.SetTop(ellipse, e.GetPosition(MapImg).Y);
}

Update width of the rectangle when changing the width of the grid

hello I have the following problem: I want to draw a rectangle on the canvas with methods Canvas.SetLeft() and Canvas.SetTop().
I use the method UserControl_Loaded() and everything works.
the problem is that having ActualWidth when resizing the window and therefore the grid, the value does not change and I left with the values no longer accurate.
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
Rectangle rett = new Rectangle();
rett.Height = grid1.ActualHeight-10;
rett.Width = grid1.ActualWidth -10;
rett.Fill = new SolidColorBrush(Colors.LightBlue);
canv.Children.Add(rett);
Canvas.SetLeft(rett, 10);
Canvas.SetTop(rett, 10);
}
this is the xaml:
<Grid x:Name="grid1">
<Canvas x:Name="canv" Height="auto" Width="auto"></Canvas>
</Grid>
in the first picture it is fine when not resize the window.
the second when I resize the grid remains the previous width.
I want the width of the rectangle was updated when changing the width of the grid.
Thank you.
Without a good, minimal, complete code example that clearly illustrates your question, along with a detailed explanation of what you're actually trying to accomplish (especially in a broader sense), it is impossible to know for sure what the best answer in your case would be.
Taking your question literally, it seems one possible approach would be to bind the Rectangle dimensions to the Grid's dimensions, so that they are updated as the Grid changes size. You can use IValueConverter to subtract the appropriate amount from the actual dimensions.
But that's a fairly complicated solution for what would otherwise be a reasonably simple problem, and especially so given that you seem to be doing this in code-behind for some reason (not ideal in the first place, and setting up bindings in code-behind is particularly tedious).
Idiomatically, what you should probably be doing is not putting the Rectangle in the Canvas at all, but rather making it a child of the Grid directly. Then you can set its alignments to Stretch so that it will fill the grid cell it's in. Finally, you can set its margins so that you have the 10 pixel gap on the top and left, and no gap on the right and bottom.
For example:
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
Rectangle rett = new Rectangle();
rett.Fill = new SolidColorBrush(Colors.LightBlue);
// NOTE: technically don't need to set these, as Stretch is the default value!
rett.HorizontalAlignment = HorizontalAlignment.Stretch;
rett.VerticalAlignment = VerticalAlignment .Stretch;
// 10 pixels of margin on top and left, none on right and bottom
rett.Margin = new Thickness(10, 10, 0, 0);
grid1.Children.Add(rett);
}
Doing it as above allows the XAML layout engine to automatically handle the resizing behavior you are looking for.
All that said, I would definitely encourage you to implement this in XAML instead of code-behind. There are a lot of things code-behind is good at, but frankly XAML is much better at any of the things directly related to the configuration of your GUI object graph.

How to draw a rectangle on a WPF canvas

I'm trying to draw a Rectangle on a Canvas as follows:
System.Windows.Shapes.Rectangle rect;
rect = new System.Windows.Shapes.Rectangle();
rect.Stroke = new SolidColorBrush(Colors.Black);
rect.Fill = new SolidColorBrush(Colors.Black);
rect.Width=200;
rect.Height=200;
Canvas.SetLeft(rect,0);
Canvas.SetTop(rect,0);
front_canvas.Children.Add(rect);
Why would this code not draw a rectangle?
The canvas is defined in the associated XAML as follows:
<Canvas Height="200" Width="200" Name="front_canvas" Grid.Row="1" Grid.Column="0">
</Canvas>
The canvas shows up fine. I can tell because of the gap it leaves in the layout grid.
This should draw your rectangle as a 200x200 black square, provided front_canvas is displayed correctly.
Why would this code not draw a rectangle?
The main reasons this would not draw are:
front_canvas is not visible
front_canvas is not in the visual tree and being displayed correctly
Some other FrameworkElement is obscuring front_canvas, at least the upper left corner.
There is another object in the canvas at a higher z order.
Note that you'd typically also want to set StrokeThickness if you want to see the Stroke you specify.
To View Rectangle you must specify the StrokeThickness and set any Integer value greater than zero:
rect.StrokeThickness=2;
// ...
front_canvas.Children.Add(rect);
Size size = new Size(front_canvas.Width, front_canvas.Height);
front_canvas.Measure(size);
front_canvas.Arrange(new Rect(size));

Categories

Resources