wpf histogram fidelity degrades when resized smaller - wpf

I've developed a simple Histogram control that shows the distribution of grayscale colors (1 to 256 bins) in a live image. The control renders rectangles in an ItemsControl whose ItemsContainer is a ViewBox. Everything is working fine for the most part, however when I resize the grid column (using a GridSplitter) that is hosting the control the fidelity of my histogram begins to degrade.
Here's a couple shots of the histogram at its initial state, and then when it has been resized horizontally (notice the dark vertical lines in the forest of green rectangles...it gets worse the smaller I go):
Here's the XAML that renders the histogram:
<ItemsControl x:Name="_Histogram" Margin="1,3"
ItemsSource="{Binding HistogramCollection}"
VerticalAlignment="Bottom">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<Grid>
<Viewbox Stretch="Fill" MaxHeight="100" >
<ItemsPresenter />
</Viewbox>
</Grid>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Rectangle Fill="LimeGreen"
Stretch="Fill"
Height="{Binding Bin}"
MinWidth="1"
StrokeThickness="0"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
RenderOptions.EdgeMode="Aliased"
UseLayoutRounding="True"
MouseEnter="Rectangle_MouseEnter"
MouseLeave="Rectangle_MouseLeave"
/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Any thoughts? My guess is that WPF is throwing out some Visuals in order to evenly distribute the rest.
I've experimented with using a Polygon in lieu of ItemsControl and Rectangles to distribute my bin points and the behavior goes away, however, I need the ability to MouseOver the histogram and show a popup (among other things, e.g. select a range of bins and update properties in my ViewModel). If you know of a way, using the Polygon approach, that I can hover in the green area and determine that I'm over a specific bin I'm definitely open to that.
UPDATE:
Removing RenderOptions.EdgeMode="Aliased" on the Rectangle XAML seems to solve the problem of black line glitches. Though it does render somewhat pale in the Designer (using d:DesignData), but it seems to render fine at runtime. I'm going to hold off on marking as "Answered" until others have had a chance to chime in. I'm really interested in the polygon approach because I get a crisper histogram when resizing to a larger width (the wider I make the histogram, the more pixelated the peaks get, i.e. the peaks are flat because of the width of the rectangle, which presents as a jagged plot instead of smooth curved plot). If only Point had a mouse over handler :D.

Related

Control size of Canvas in ScrollViewer

I am writing a custom control for image display and interaction. Some of the features it needs is zooming and panning as well as drawing a custom region of interest, i.e. drawing a rectangle on top of the displayed frame with arbitrary position and size. Zooming and panning features are done and work nicely, I'm using a ScrollViewer, a scale transform and some event handlers for mouse clicking and moving in the C# code.
For the custom selection feature I want to add a canvas on top of the frame since that's the only way I know of that allows arbitrary positioning of a rectangle. In order for any shapes on the canvas to be mappable to image coordinates I need of course the canvas to exactly line up with the image so size and position must be the same. I also would like the image to keep adjusting to window resizes as it does without any canvas. Since Canvas doesn't automatically resize itself to anything I want to bind its dimensions to the size of the image like so (this is from the control template for the custom control):
<ScrollViewer x:Name="scrollViewer" Grid.Column="0" Grid.Row="1" CanContentScroll="True" PanningMode="Both" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<Grid>
<Image x:Name="screen" Panel.ZIndex="0" Cursor="Cross" Source="{TemplateBinding ColoredFrame}"/
<Canvas x:Name="testCanvas" Panel.ZIndex="1" Background="Transparent" Width="{Binding ActualWidth, ElementName=screen}" Height="{Binding ActualHeight, ElementName=screen}"/>
<Grid.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="{Binding RotationAngle, RelativeSource={RelativeSource TemplatedParent}}"/>
<ScaleTransform x:Name="scaleTransform" ScaleX="{Binding ZoomFactor, RelativeSource {RelativeSource TemplatedParent}}" ScaleY="{Binding ZoomFactor, RelativeSource={RelativeSource TemplatedParent}}"/>
</TransformGroup>
</Grid.LayoutTransform>
</Grid>
</ScrollViewer>
However, I am getting weird behaviour with this - when I use bindings for both Width and Height of the canvas to ActualWidth and ActualHeight of the image, like in the above code, it will automatically resize itself only when the size increases (when the user increases the size of the window) but not when it decreases. Any size increase is permanent and when decreasing the window again, one would have to scroll as the size of the image and canvas never decrease. This does not happen when I bind only one of the dimensions (width or height) and set the other one to a constant value. Binding modes (one way / two way) have no effect either.
I'm probably missing something really obvious but I've spent hours trying to figure this out and I am at a loss. Any help would be appreciated.
as far as I know Canvas does not have good sizing options. I think an Adorner will do what you try to achieve. For an example and additional information please have a look here: https://learn.microsoft.com/de-de/dotnet/framework/wpf/controls/adorners-overview
Happy coding
Tim

How do I properly draw and scale a Canvas as a WPF background to a control?

I have a StackPanel that needs to contain drawn background. Specifically, my StackPanel needs to have the ability to grow and the rectangle must grow with the StackPanel, but must remain pseudo-anchored to each side at a fixed position.
I've attempted to use the Canvas.Left, Canvas.Right, Canvas.Top and Canvas.Bottom attached properties, but so far they've not worked. Furthermore, this does seem to work when drawing within Canvas objects, just not when they are embedded within a VisualBrush set as a background. How can I accomplish drawing this resizable, rectangular background within my StackPanel?
Below is the state of my current code. I've tried various approaches but none seem to work.
My Code:
<StackPanel DockPanel.Dock="Right" Orientation="Vertical" VerticalAlignment="Center">
<StackPanel.Background>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<Canvas Background="Magenta" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
Rectangle Fill="#FFDDECF7" Canvas.Left="20" Canvas.Top="20" Canvas.Bottom="20" Canvas.Right="0"/>
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
</StackPanel.Background>
...
</StackPanel>
This currently doesn't render anything. I set the canvas background to magenta just so I could see if it were drawing, and I'm not even seeing that. Other attempts have drawn the canvase, however, the blue rectangle is always stretched to fill the window, regardless of attached canvas property settings.
Sample:
The image below is a sample of what I want. Again, I'm using an ugly Magenta color to show the offset of the internal, blue rectangle. As the StackPanel grows or shrinks, the rectangle needs to be affixed to the top, left, right and bottom.
My suggestion is to place the stackpanel inside a grid:
<Grid DockPanel.Dock="Right" VerticalAlignment="Center" Background="Magenta">
<Rectangle Margin="20" Fill="#FFDDECF7"/>
<StackPanel Orientation="Vertical">
no background...
</StackPanel>
</Grid>
Wrap Canvas into a ViewBox, then work on a ViewBox. As far as I know Canvas doesn't support scalling too well.

Is there a mathematical formula that can be applied to determine margin and (possibly) font size

In a wpf user control I'm creating the end user has the option to alter the default size of the included images. When Originally designing the control I based my eventual margin sizes on trial and error, eventually settling on what looked right to me.
When image sizes were either 16 x 16 or 24 x 24 the overall look is reasonable;
but once you start to dramatically increase the image size to two textboxes and text block in the control get thrown out of sync with the images.
The xaml that I originally used for the text and text block is shown below (it also includes the find button in the middle of them but helps by showing how all buttons are defined).
<TextBox x:Name="Record" BorderThickness="0" Margin="6" ></TextBox>
<Button Background="#00000000" x:Name="Find" Height="Auto" Width="Auto"
ToolTip="Find the Record at this position." BorderThickness="0">
<StackPanel Margin="2" Orientation="Horizontal">
<Image Source="{Binding ImageFind, ElementName=DN}"
Width="{Binding ImageFindWidth, ElementName=DN}"
Height="{Binding ImageFindHeight, ElementName=DN}"/>
<TextBlock Margin="3" Text="{Binding TextFind,ElementName=DN}"/>
</StackPanel>
</Button>
<TextBlock Margin="6">of</TextBlock>
<TextBox x:Name="Records" BorderThickness="0"
Margin="6"
Text="{Binding RecordsCount, ElementName=DN}"></TextBox>
All of the buttons and text boxes are contained in a stack panel with horizontal orientation set.
What I would like to know if is there is a mathematical formula that I could apply to the textboxes and textblock at the time that the end user changes the width height of the images (they will always be the same in either dimension) that would correct the layout , thus centring the text in line with the middle of the images.
Possibly there is some other wpf layout trick about which I have yet to learn that does this sort of thing, either way I'd welcome suggestions.
Thanks
Set the VerticalAlignment property on the TextBlock and VerticalContentAlignment on the TextBox.
<TextBlock Margin="6" VerticalAlignment='Center'>of</TextBlock>

Silverlight CliptoBound property issue

I am working on a Silverlight application and my problem is like this: I have a WrapPanel
and inside the WrapPanel I am adding a number of images. Then, I rotate these images either
by 90 degrees or -90 degrees.
So if my image size is 200 by 250, when I rotate it it will go out of the WrapPanel by 50 pixels. Is there a way to clip this image to the WrapPanel's actual bounds?
Sounds to me like you are using the RenderTransform to rotate the image. The problem is the transform is applied after the size and position of the image has been allocated hence the rotated image bleads outside of its original rectangle.
One solution is to use the LayoutTransfomer to effect that transform instead. This would cause the WrapPanel to re-flow the elements even moving the image to the next row. Here is an example using rectangles for simplicity:-
<ScrollViewer HorizontalScrollBarVisibility="Disabled">
<ItemsControl ItemsSource="{StaticResource TestData}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<toolkit:LayoutTransformer RenderTransformOrigin="0.5, 0.5" Margin="5">
<toolkit:LayoutTransformer.LayoutTransform >
<RotateTransform Angle="0" />
</toolkit:LayoutTransformer.LayoutTransform>
<Rectangle Width="100" Height="150" Fill="Blue" MouseLeftButtonUp="Rectangle_MouseLeftButtonUp" />
</toolkit:LayoutTransformer>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
I've added code-behind:-
private void Rectangle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
LayoutTransformer lt = ((FrameworkElement)sender).Parent as LayoutTransformer;
RotateTransform rt = (RotateTransform)lt.LayoutTransform;
rt.Angle += 90;
lt.ApplyLayoutTransform();
}
My TestData items source is an arbitary List<T>, doesn't really matter what is in the list just that it contains a few items.
Using the ScrollViewer as dynamic clipping region
Note that its normal to place a WrapPanel in a ScrollViewer which naturally clips its content anywar.
Perhaps you really do want to clip the WrapPanel and you don't want any scrolling. Obviously you could apply a RectangleGeometry its Clip property but you would have to ensure the clip rectangle always matched the actual size of the WrapPanel. You could do that by monitoring its SizeChanged event and using a bit of code.
Another approach is to put it in a ScrollViewer then disable both scroll bars.
Just to add to AnthonyWJones answer above, which is entirely correct, you need to rotate during layout rather than render. Silverlight Panels do not clip their contents, in order to perform clipping you must set their Clip property to the required geometry manually.
The following blog post provides an attached behaviour that will automatically clip the bounds of a panel:
http://www.scottlogic.co.uk/blog/colin/2009/05/silverlight-cliptobounds-can-i-clip-it-yes-you-can/
You simply set the property as follows:
<Canvas util:Clip.ToBounds="true"
Grid.Column="1" Background="Aqua" Margin="20" >
<Ellipse Fill="Red" Canvas.Top="-10"
Canvas.Left="-10" Width="130" Height="130"/>
</Canvas>

WPF zoom canvas and maintain scroll position

I have a Canvas element, contained within a ScrollViewer, which I'm zooming using ScaleTransform. However, I want to be able to keep the scroll position of the viewer focused on the same part of the canvas after the zoom operation has finished. Currently when I zoom the canvas the scroll position of the viewer stays where it was and the place the user was viewing is lost.
I'm still learning WPF, and I've been going backwards and forwards a bit on this, but I can't figure out a nice XAML based way to accomplish what I want. Any help in this matter would be greatly appreciated and would aid me in my learning process.
Here is the kind of code I'm using...
<Grid>
<ScrollViewer Name="TrackScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Canvas Width="2560" Height="2560" Name="TrackCanvas">
<Canvas.LayoutTransform>
<ScaleTransform ScaleX="{Binding ElementName=ZoomSlider, Path=Value}"
ScaleY="{Binding ElementName=ZoomSlider, Path=Value}"/>
</Canvas.LayoutTransform>
<!-- Some complex geometry describing a motor racing circuit -->
</Canvas>
</ScrollViewer>
<StackPanel Orientation="Horizontal" Margin="8" VerticalAlignment="Top" HorizontalAlignment="Left">
<Slider Name="ZoomSlider" Width="80" Minimum="0.1" Maximum="10" Value="1"/>
<TextBlock Margin="4,0,0,0" VerticalAlignment="Center" Text="{Binding ElementName=ZoomSlider, Path=Value, StringFormat=F1}"/>
</StackPanel>
</Grid>
This is not a purely XAML way of doing it, but there is a very nice piece of work on Joeyw's blog titled Pan and Zoom (DeepZoom style) in WPF with links to the source. He has taken some inspiration from DeepZoom and it gives you smooth/animated panning and zooming of content. And if you're using WPF 4 you could probably modify it a little to add some easing functions to the animations to give it an even nicer feel.

Resources