TreeView/ScrollView rendering bug with bitmaps? - wpf

There is a WPF (.NET 3.5) TreeView, which contains bitmaps.
Nothing special:
<ControlTemplate x:Key="ctrlOutputTree" TargetType="{x:Type vo:OutputTreeView}">
<TreeView x:Name="OutputTree" ItemContainerStyle="{DynamicResource TreeViewItemStyle}"
KeyboardNavigation.TabNavigation="Cycle" SnapsToDevicePixels="True" MouseDown="OutputTree_MouseDown"
Loaded="OutputTree_Loaded" RenderOptions.BitmapScalingMode="NearestNeighbor">
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<EventSetter Event="RequestBringIntoView" Handler="OutputTree_RequestBringIntoView"/>
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded, Mode=TwoWay}" />
</Style>
</TreeView.Resources>
<TreeViewItem ItemsSource="{Binding Converter={StaticResource featureDiffsSourceCreator},Mode=OneWay}"
IsExpanded="True" Margin="0,4,0,0"
KeyDown="AttrDiffTreeviewItem_KeyDown">
</TreeViewItem>
</TreeView>
</ControlTemplate>
TreeViewItems:
<ControlTemplate x:Key="ImageTemplate">
<Image VerticalAlignment="Top" Margin="3,1,0,0"
RenderOptions.BitmapScalingMode="NearestNeighbor"
RenderOptions.EdgeMode="Aliased"
Stretch="None">
<Image.Source>
<!-- <BitmapImage UriSource="c:\\imageBMP_test.bmp" />-->
<MultiBinding Converter="{StaticResource imageConverter}">
<Binding Path=....
</MultiBinding>
</Image.Source>
</v:Image>
</ControlTemplate>
The bitmaps are generated as BitmapSource in imageConverter (from native control).
System::Drawing::Bitmap^ b = System::Drawing::Image::FromHbitmap((IntPtr)aBmp);
IntPtr hb = b->GetHbitmap();
System::Windows::Media::Imaging::BitmapSource^ bs =
System::Windows::Interop::Imaging::CreateBitmapSourceFromHBitmap(hb,
System::IntPtr::Zero,
Int32Rect(0,0,nWidth,nHeight),
BitmapSizeOptions::FromEmptyOptions());
It displays all fine, but when we scroll the tree, sometimes bitmaps becomes distorted. Mostly close to the bottom. Sometimes we need to resize the tree to happen, but it happens sooner or later.
ItemX is text, and the table next to it is the bitmap.
After this, when it's wrong, if I scroll the tree with the scrollbar-position-thumb, just 1 pixel up/down, it becomes sharp and good again.
But if I scroll with up/down arrows on the scrollbar, bitmaps remain invalid for a few click (then goes away)
If I replace the BitmapSource generation in XAML MultiBinding/imageConverter --> <BitmapImage UriSource="c:\\imageBMP_test.bmp" , then don't experience this issue. (I have to take care the imageBMP_test.bmp to be 24 bits & 96DPI, otherwise also not good)
If I change the BitmapSource generation to read from file (24 or 32bit bmp with 96dpi):
System::Drawing::Bitmap^ b = gcnew System::Drawing::Bitmap("c:\\imageBMP24_96dpi_small.bmp");
IntPtr hb = b->GetHbitmap();
System::Windows::Media::Imaging::BitmapSource^ bs =
System::Windows::Interop::Imaging::CreateBitmapSourceFromHBitmap(hb,
System::IntPtr::Zero,
Int32Rect(0,0,nWidth,nHeight),
BitmapSizeOptions::FromEmptyOptions());
Then after some scrolling, I got this:
Any help, idea would be appreciated.
What I tried:
Checked, generated 'BitmapSource' is 96 DPI.
Saved into file all generated 'BitmapSource' - all were fine, while they were distorted on the screen.
I am out of ideas, seems it's a WPF bug? Or maybe video driver? (nvidia quadro/laptop/win7 64bit)
SO IN SHORT
TreeView displays bitmap loaded from file specified in XAML: OK (very small glitches here also sometimes, but those are acceptable)
TreeView displays bitmap from BitmapSource generated: FAILS sometimes (during scroll)

I found a solution.
Have no clue why it works, but seems to solve the problem:
if in the imageConverter (in a native control) I return BitmapImage^ instead of BitmapSource^, it's ok.
Very strange, but I just create BitmapImage from the BitmapSource, return that, and no problems.
WPF is horrible...

Related

Why does this image upscale from its original size?

I am having an issue with an image scaling up slightly, seemingly without explanation. The image is a 16x16 png icon but seems to be scaling slightly larger to roughly 21x21. In my application there are a dozen or so buttons on a toolbar with 16x16 png icons in them but one of them scales up so said button ends up larger than all the rest. The image in question is CollapseAll_16x.png from the Visual Studio 2019 Image Library which can be downloaded from Microsoft's website. The rest of the images are all either from the library as well or custom 16x16 images I've designed. The CollapseAll image has not been altered in any way and is the only image that is affected by this.
Below is some simplified code that shows the issue I'm describing. All images are from the VS2019 Image Library. Borders and ActualWidth added for reference. The toolbar and button set up is irrelevant to the issue as it is still happening in this example stripped-down to just the images in a stackpanel.
<StackPanel>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</StackPanel.Resources>
<Border>
<Image x:Name="collapseAll" Source="Icons/CollapseAll_16x.png"/>
</Border>
<Border>
<Image x:Name="zoomIn" Source="Icons/ZoomIn_16x.png"/>
</Border>
<Border>
<Image x:Name="zoomOut" Source="Icons/ZoomOut_16x.png"/>
</Border>
<Border>
<Image x:Name="popOut" Source="Icons/PopOut_16x.png"/>
</Border>
<Border>
<Image x:Name="close" Source="Icons/Close_16x.png"/>
</Border>
</StackPanel>
<TextBlock Text="{Binding ActualWidth, ElementName=collapseAll}"/>
<TextBlock Text="{Binding ActualWidth, ElementName=zoomIn}"/>
<TextBlock Text="{Binding ActualWidth, ElementName=zoomOut}"/>
<TextBlock Text="{Binding ActualWidth, ElementName=popOut}"/>
<TextBlock Text="{Binding ActualWidth, ElementName=close}"/>
</StackPanel>
This is the result:
I am able to prevent this unwanted scaling by adding MaxWidth=16 and/or MaxHeight=16 to the collapseAll image element. In that case all the images display the same.
So my question isn't so much how to fix this but, rather, why this particular image refuses to display at its original size.
PNG images are usually set to a default density of 96ppi to match traditional screen pixels per inch at nominal 96 DPI
Most PNGs thus contain such flags for applications to recognise and use if they use a default value. So the "Zoomin" Etc have such a marker. (0.17 inches square)
HOWEVER save this copy pasted from clipboard and it should report as null DPI and have a size of 0.5644 x 0.5644 cm; 0.22 x 0.22 inches!!
A common alternative is PDF or Jpeg default resolution of 72 pixels per inch which is 1.33% larger. And that CollapseAll icon has such a marker. (0.22 inches square) So it is "displaying" at its natural scale !
So unless ppi or DPI is defined one will be larger in the ratio of 3:4 pixels per inch. (75% or 133.3%)
there are 29 such files
AddPackage_16x.png BackgroundWorker_16x.png
CallFrom_16x.png Close_12x_16x.png
CloudDownload_16x.png CloudUpload_16x.png
CollapseAll_16x.png CPPMarkupXML_16x.png
DatabaseSchema_16x.png DeleteAllRows_16x.png
ExpandAll_16x.png ExtractConstant_16x.png
LayerMask_16x.png LinesToFile_16x.png
OpenProject_16x.png Output_16x.png
RFile_16x.png RInteractiveWindow_16x.png
R_16x.png R_ProjectSENode_16x.png
ShowCallersGraph_16x.png SortAscending_16x.png
SortDescendingFilter_16x.png StatusSuppressed_16x.png
StatusUpdate_16x.png SyncArrow_inverse_16x.png
Synchronize_16x.png Synchronize_grey_16x.png
ToggleButton_16x.png
29 File(s) 10,420 bytes

ZIndex for one canvas over an adjacent canvas

I'm trying to render a row of interlocking arrows, like this:
Each arrow represents a step in a workflow, and the color of each arrow is determined by its step's position in the workflow. While the example above has 3 colors (before, current, after), so far I've implemented only 2 colors (before/current, after) and that's fine for the purposes of this question. I've got an IMultiValueConverter to handle those colors.
The workflow steps are represented as a StepStruct which has a Step property (set manually, more on this later), and a VmFunc property which returns the view model for that step. My current IMultiValueConverter uses the index of the step in the list of steps, rather than the actual Step value.
My problem is the arrows. Initially I have each step as a canvas rendering a simple rectangle, and that's easy to get working. But to make the arrow, I've used a PolyLine that I want to position at the right of the canvas, indeed starting at the right of one canvas and overflowing into the next.
I can't get the Panel.ZIndex to work in such a way that a canvas's arrow is visible overflowing into its right-hand neighbor.
My code is pasted below. Of note:
I've bound the Polyline's Panel.Zindex (which I've read affects its parent canvas's z-index) to the StepStruct's Step property, and I've set the Step values so that each step's value is less than the value of the step to its left, which should show left steps over the right step.
I've commented a line setting the Canvas.Left property of each PolyLine. When I uncomment this line (and move it into place), the Polyline indeed moves over to the right side of the canvas, but it's invisible, which I imagine is because it's hidden behind the canvas to its right. (I've confirmed this by changing the PolyLine to start with negative x-values, so you can see the part that's not blocked by the neighboring canvas. This is not pictured.)
I've rendered each canvas's Step as text, and those TextBlocks all have an identical z-index (40) so that they're in front of some PolyLines and behind others, which shows that the PolyLine does have its z-index set right, at least within its own canvas.
<Border Grid.Row="0" BorderBrush="{StaticResource BackgroundBlack}" BorderThickness="2">
<!--Progress bar-->
<ItemsControl ItemsSource="{Binding StepViewModels}" Grid.Row="0" x:Name="progressBar">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas Height="24">
<Canvas.Style>
<Style TargetType="Canvas">
<Setter Property="Background" Value="{StaticResource BackgroundDarkGrey}"/>
<Style.Triggers>
<DataTrigger>
<DataTrigger.Binding>
<!-- the binding. this part works so I'm omitting the code -->
</DataTrigger.Binding>
<DataTrigger.Value>True</DataTrigger.Value>
<Setter Property="Background" Value="{StaticResource DisabledGrey}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Canvas.Style>
<Polyline Points="0,-1 20,11 0,25" Stroke="{StaticResource BackgroundBlack}" StrokeThickness="2"
Fill="{Binding Background, RelativeSource={RelativeSource AncestorType=Canvas}}"
Panel.ZIndex="{Binding Step}"
/>
<!--Canvas.Left="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=Canvas}}"-->
<TextBlock Text="{Binding Step}" FontWeight="ExtraBlack" Panel.ZIndex="40" Foreground="Pink" FontSize="20" Canvas.Left="8"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
The result (with the Canvas.Left still commented)
How do I get the arrows to render at the end of one canvas and atop the next, as desired?
(I could of course keep it this way and make it work but the bindings would be very annoying and complicated, needing to bind each arrow to the color of the previous canvas; or rather do some kind of conversion involving IndexOf - 1)
For ZIndex:
Each item is wrapped by a ContentPresenter, this is done by the ItemContainerGenerator inside this ItemsControl instance.
So you need to edit the container to get a new ZIndex(via a ContentPresenter style and a binding for example).
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Panel.ZIndex" Value="{Binding XXX}" />
</Style>
</ItemsControl.ItemContainerStyle>
For the shape:
I would recommend using a Path control with a predefined shape geometry like:
"M 0,0 L30,0 L34,5 L30,10 L0,10 z"
and have a negative margin at the end like Margin="0,0,-4,0"

WPF toolkit charting : Customize DataPoint ToolTip

I would like to add a tooltip over the datapoint of a lineseries that shows both the X and Y values (independent and dependent values), rather than just the dependent value that appears by default. I am aware this is the same question as was written in this ticket - WPF toolkit charting : Customize datapoint label
However, I can't get the answer to work. There is a link to more detail that appears to be outdated.
My line series:
<DVC:Chart.Series>
<!--Have several lineseries that look like this, connected to a styling vm. Can add ToolTip=...-->
<VM:LineSeries x:Name="something"
Title="something"
DependentValuePath="Value"
IndependentValuePath="Key"
ItemsSource="{Binding something}"
DataPointStyle="{StaticResource DataPointBlue}"
>
</VM:LineSeries>
</DVC:Chart.Series>
My datapoints are styled here, but adding a setter property with any tooltip doesn't make a difference:
<UserControl.Resources>
<Style x:Key="DataPointBlue" TargetType="{x:Type DVC:DataPoint}">
<Setter Property="Background" Value="Blue"/>
</Style>
</UserControl.Resources>
I've tried adding this line of code from the above linked ticket in several places in a variety of ways, and I've tried using Binding in various ways, but nothing has hit the mark.
<ToolTipService.ToolTip>
<StackPanel Margin="2,2,2,2">
<ContentControl Content="{TemplateBinding IndependentValue}" />
<ContentControl Content="{TemplateBinding DependentValue}" />
</StackPanel>
</ToolTipService.ToolTip>
This has been a lot of trial and error that hasn't been making progress.

Scaling images in a WPF ListBox

I've started developing my code using the example from SO WPF: arranging collection items in a grid. Now, to gain cell selection capability, I renamed each ItemsControl to ListBox, because a ListBox is-a ItemsControl (XAMl somewhat simplified):
<ListBox HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" ItemsSource="{Binding YourItems}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style>
<Setter Property="Grid.Column" Value="{Binding X}"/>
<Setter Property="Grid.Row" Value="{Binding Y}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Image RenderOptions.BitmapScalingMode="LowQuality" Source="{Binding ...ImageSource, Mode=OneWay}">
</Image>
</DataTemplate>
</ListBox.ItemTemplate>
The grid is filled with glyph run test imaged, based on the code here.
Surprisingly it worked - kind of. Selection works. However, in the case of the ItemsControl, there was no scroll bar. Everything scaled nicely. When I made the window smaller, the grid cells shrunk and so did the images. When I made the window larger, everything scaled up.
Now, with the ListBox that's not the case. The images size stays fixed. If the window isn't large enough, there's an horizontal scroll bar and when the window isn't large enough, some of the images are hidden and the user needs to scroll to the right.
So, my question is: If a ListBox is-an ItemControl, why don't my images scale the same? What should I do to correct it?
This is because ListBox and ItemsControl use different styles. You might easily apply the ItemControl's default style to your ListBox:
<ListBox Style="{StaticResource ResourceKey={x:Type ItemsControl}}">

Custom Control TemplateBinding question

Imagine a form designer with a grid overlay that would represent coordinates on a plane. I'm trying to bind the properties of the grid overlay to the Canvas within a custom ItemsControl.
The grid is created using a VisualBrush. The VisualBrush's Viewbox and Viewport are bound to a Rect in the code, as well are the Height and Width of the Rectangle used to display the grid tile. However, when the control displays, the grid tiles seem to be "infinitely small" (the grid is just grey) in that if I zoom into the grid, the program will eventually just seize up, unable to render it. Obviously, this is not the effect I'm going for.
<Style TargetType="{x:Type Controls:FormControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Controls:FormControl}">
<Border Background="White"
BorderBrush="Black"
BorderThickness="1"
Padding="49">
<Grid Height="{TemplateBinding CanvasHeight}"
Width="{TemplateBinding CanvasWidth}">
<Grid.Background>
<!--"0,0,6,11.3266666666667"-->
<VisualBrush TileMode="Tile"
Viewbox="{TemplateBinding GridUnitViewbox}"
ViewboxUnits="Absolute"
Viewport="{TemplateBinding GridUnitViewbox}"
ViewportUnits="Absolute">
<VisualBrush.Visual>
<Rectangle Height="{Binding RelativeSource={RelativeSource TemplatedParent},Path=GridUnitViewbox.Height}"
HorizontalAlignment="Left"
Opacity="{TemplateBinding GridOpacity}"
Stroke="Black"
StrokeThickness=".1"
VerticalAlignment="Top"
Width="{Binding RelativeSource={RelativeSource TemplatedParent},Path=GridUnitViewbox.Width}" />
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
<ItemsPresenter />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="Controls:FormControlItem">
<Setter Property="Canvas.Left"
Value="{Binding Path=X}" />
<Setter Property="Canvas.Top"
Value="{Binding Path=Y}" />
</Style>
</Setter.Value>
</Setter>
</Style>
Any idea what I am doing wrong here. Thanks.
EDIT:
Maybe a little more background of what I'm doing may put it in better context. I work for a tax preparation software company and, currently, our forms division creates substitute forms using a markup that was written for our product like a million years ago. It's a bit cumbersome creating forms this way, so I'm developing a visual "Form Designer" for them that will be more like a WYSIWYG and translate the contents of the designer into markup. Well, the IRS is real anal about everything on the form being EXACTLY where it was on the original, so there is a very loose standard by which a "grid overlay" can be placed over the form to determine where things need to go; basically a coordinate plane of sorts.
FormControl is essentially the visual representation of one of these substitute forms that one of the forms designers would be creating.
CanvasWidth and CanvasHeight are CLR wrappers to Dependency Properties. They are assigned values in OnApplyTemplate() by multiplying the dimensions of GridUnitViewbox by however many grid tiles need to be in the grid overlay, ie. a 78x63 grid in most cases.
The names CanvasWidth and CanvasHeight I think might be a little misleading in that they do not refer to the Canvas control, but to the Grid that houses the Canvas (probably need to change the naming convention). That said, CanvasHeight, CanvasWidth and GridUnitViewbox are not dependent on any control's properties, but rather calculations that are done in OnApplyTemplate().
public static readonly DependencyProperty GridUnitViewboxProperty =
DependencyProperty.Register("GridUnitViewbox", typeof(Rect), typeof(FormControl),
new FrameworkPropertyMetadata(new Rect(0, 0, 6, 11.3266666666667),
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsParentMeasure));
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
FormattedText formattedText = new FormattedText(
"X",
CultureInfo.GetCultureInfo("en-us"),
FlowDirection.LeftToRight,
new Typeface("Courier New"),
10,
Brushes.Black);
this.GridUnitViewbox = new Rect(0, 0, formattedText.WidthIncludingTrailingWhitespace, formattedText.Height);
if (this.PageLayout == PageLayoutType.Landscape)
{
this.CanvasHeight = this.GridUnitViewbox.Height * 48.0;
this.CanvasWidth = this.GridUnitViewbox.Width * 109.0;
}
if (this.PageLayout == PageLayoutType.Portrait)
{
this.CanvasHeight = this.GridUnitViewbox.Height * 63.0;
this.CanvasWidth = this.GridUnitViewbox.Width * 78.0;
}
}
The Grid control is actually doing exactly what I want it to do. It is the VisualBrush and the Rectangle within that are either not binding correctly or are not being updated properly. The comment right below <Grid.Background> was the hard-coded testing value that I was using for VisualBrush's Viewbox and Viewport as well as Rectangle's dimensions before I got around to binding the values and it produced, visually, exactly what I was going for. I've also confirmed that 6 and 11.3266666666667 are, in fact, the values for the GridUnitViewbox's dimensions during runtime.
I have a feeling that the binding is producing '0,0,0,0' however, because the grid overlay is just a grey shading that is eating up an immense amount of resources; locks the program, in fact, if you zoom into it. As you can see, in the code I tried adding AffectsMeasure and AffectsParentMeasure to the Metadata options of the dependency property in hopes that perhaps the UI was not updating properly after GridUnitViewbox's dimensions were updated, but I was wrong. I'm not sure what else it could be.
Alright, just in case anyone else encounters a similar issue, I found the workaround. Apparently VisualBrush is a little finicky about TemplateBindings. I adjusted the XAML thusly and it solved the problem:
<VisualBrush TileMode="Tile"
Viewbox="{Binding RelativeSource={RelativeSource TemplatedParent},Path=GridUnitViewbox}"
ViewboxUnits="Absolute"
Viewport="{Binding RelativeSource={RelativeSource TemplatedParent},Path=GridUnitViewbox}"
ViewportUnits="Absolute">
Here is the article where I got the information from.
What is CanvasWidth and CanvasHeight? Are they defined?
Also, I believe that a canvas has no size unless explicitly specified. When you bind to it, the width/height may be zero.
Try ActualWidth and ActualHeight or the canvas after the rendering pass to see if they contain any values, but afaik they don't unless you provide one.

Resources