Count the number of displayed VisualBrush visuals - c#

I am working on a CAD program in WPF and I'm looking for a way to count the number of controls displayed when a particular brush is rendered.
So say I have an Ellipse:
<Ellipse x:Name="Ellipse" Canvas.Top="25" Canvas.Left="50" Width="400" Height="250" Stroke="DarkBlue" StrokeThickness="5" />
And I fill it with a VisualBrush from code behind:
VisualBrush tileCounter = new VisualBrush();
Rectangle rect = new Rectangle() { Width = 10, Height = 10, Fill = Brushes.Blue, Stroke = Brushes.BlueViolet, StrokeThickness = 1 };
tileCounter.Visual = rect;
tileCounter.TileMode = TileMode.Tile;
tileCounter.Stretch = Stretch.None;
tileCounter.Viewport = new Rect(0, 0, 10, 10);
tileCounter.ViewportUnits = BrushMappingMode.Absolute;
Ellipse.Fill = tileCounter;
Is there any way to get the VisualBrush to report back how many instances of the rectangle shape it has rendered as the fill of the Ellipse? Or are there code changes I could make to reference each visual individually from the parent using the Fill or Background?
I am currently working on a tool to draw figures that have any number of sides that are LineSegment, ArcSegment, or QuadraticBezierSegment and the brush is a grid that the user defines with entered hight, width, and grid size. The grid is also able to be realigned by the user. This makes simple mathematical solutions extremely hard to pull off and so a WPF solution would be preferable.
My ultimate goal is to get total number of visuals it attempts to render and then how much of each visual is rendered across the entire fill.

I'm sorry to tell you but I think you have to go the mathematical route.
Though I'm not entirely, sure I'd assume wpf is drawing that rect once and then caching the result, otherwise that visualbrush would not be performant. So essentially it really is only a texture, nothing where you could be aware of how many tiles have been drawn entirely or partially.

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!

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

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);
}

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));

How can I use vector graphics (defined in XAML) as the image of a Ribbon menu item?

I need to convert my WPF application, which has a Ribbon, to use vector graphics for the images on buttons, etc., instead of bitmaps. I've got this working so far, except for the RibbonApplicationMenu items, which use their RibbonApplicationMenu.ImageSource property, with type ImageSource to set the graphic that shows up at the left side of the ribbon menu item.
My vector graphics are defined like this in XAML:
<ControlTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ViewBox Width="148.854" Height="150.500">
<Canvas Width="148.854" Height="150.500">
<Path>
<!-- . . . yadda yadda yadda . . . -->
</Path>
</Canvas>
<Viewbox>
</ControlTemplate>
These render properly when I set them to be the .Template of a Button, etc.
I can't figure out, however, how to use them for the images that show up in the RibbonApplicationMenu controls. Just setting the .Template property of the RibbonApplicationMenu does not work (it fills the entire control seems to break its functionality).
I attempted to use a VisualBrush and then render it to a RenderTargetBitmap to use as the .ImageSource (I also have to work mostly in code behind for reasons I won't get into):
ContentControl cc = new ContentControl(); // just picked ContentControl as a test
cc.Template = myTemplate; // assume myTemplate has the proper ControlTemplate
VisualBrush visBrush = new VisualBrush();
visBrush.Visual = cc;
// not really sure about the parameters to this next line
RenderTargetBitmap rend =
new RenderTargetBitmap(148.854, 150.5, 120, 96, PixelFormats.Pbgra32);
rend.Render(cc);
RibbonApplicationMenuItem menu = new RibbonApplicationMenuItem();
menu.ImageSource = render;
This compiles, but just shows a blank where the image would go. If I instead use a BitmapImage loaded from an image file as the .ImageSource, it displays correctly.
Any ideas how I can get this to work?
Two things that might need to be done:
Firstly there is no explicit size set for the Canvas in the ControlTemplate, you'll need to assign it a width and height (see the upvoted answer here for details).
The other thing is that you need to Arrange and Measure the Viewbox so that it assumes the required dimensions.
So altering the xaml for the vector graphics to something like:
<ControlTemplate x:Key="Template">
<Viewbox>
<Canvas Height="100" Width="130">
<Path>
<!-- data -->
</Path>
</Canvas>
</Viewbox>
</ControlTemplate>
And the code to:
ControlTemplate controlTemplate =
FindResource("Template") as ControlTemplate;
ContentControl content = new ContentControl();
content.Template = controlTemplate;
// ensure control has dimensions
content.Measure(new Size(200, 200));
content.Arrange(new Rect(0, 0, 200, 200));
RenderTargetBitmap render =
new RenderTargetBitmap((int)content.ActualWidth, (int)content.ActualHeight, 120, 96, PixelFormats.Pbgra32);
render.Render(content);
RibbonApplicationMenuItem menu = new RibbonApplicationMenuItem();
menu.ImageSource = render;
Will hopefully get it working.

Categories

Resources