Real time opacity mask - c#

I have an collection of images that I am painting on a canvas control. I want to be able to mask and unmask parts of the canvas in real-time.
I was thinking of using a WritableBitmap for the source of the opacity mask, but WritableBitmaps are limited to drawing with a 1-pixel brush (cant change the stroke thickness).
I am able to paint on another canvas the desired opacity mask, however I would have to constantly render that canvas to an imagesource, which is super slow.
There must be a simple way.
Edit - Some details of my app:
I am creating a painting app. I draw a collection of images onto a canvas. I have a collection of these canvases (to simulate a layer system), each with their own collection of images. So I am trying to implement a feature to edit the opacity mask of each canvas (each layer) on the fly.
Here is the collection of collections code behind
<!--Outer collection-->
<ItemsControl Name="canvasDataBinding"
Focusable="True"
HorizontalAlignment="Center"
Height="512"
Width="512"
VerticalAlignment="Center"
ClipToBounds="True">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas MouseMove="Canvas_MouseMove"
MouseEnter="Canvas_MouseEnter"
MouseLeave="Canvas_MouseLeave"
MouseDown="Canvas_MouseDown"
MouseUp="Canvas_MouseUp"
Background="Transparent">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--nested collection-->
<ItemsControl Height="512" Width="512" HorizontalAlignment="Center" VerticalAlignment="Center" ItemsSource="{Binding ImageSource}" OpacityMask="{Binding Mask}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent"
Height="512"
Width="512"
HorizontalAlignment="Center"
VerticalAlignment="Center">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Name}"
Width="{Binding Width}"
Height="{Binding Height}"
Opacity="{Binding Opacity}">
<Image.RenderTransform>
<TransformGroup>
<TranslateTransform X="{Binding OffsetX}" Y="{Binding OffsetY}"/>
<RotateTransform CenterX="{Binding CenterX}" CenterY="{Binding CenterY}" Angle="{Binding Angle}"/>
</TransformGroup>
</Image.RenderTransform>
</Image>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Related

Auto-size drawing container in WPF

I'm currently working on a small C# Project to visualize our seating order in the Office. As we have multiple, different rooms, the desk layout is created dynamically. The data comes from an XML where a jpg-image (room layout) is stored as a base64 encoded string. Moreover, the XML file contains a list of all desks with their absolute pixel-Position on the Image.
Until now, I managed to draw the different room-images, each into a seperate tab page and all the desks (which are stored in a List of a "desk"-object.) at their position on the Image. All this is done in the xaml-file of the window.
My Problem is now that the canvas does not size the children, i.e. Image and desks, according to the parent's (i.e. tab page's) size.
I already tried using a grid oder putting a viewbox aroud the canvas and writing a custom canvas class by overriding the MeasureOverride() function.
Here is a minimal working example of my xaml file:
<TabControl x:Name="tabControl" Grid.Column="0">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Canvas>
<Image
Source="{Binding Image}" >
</Image>
<ItemsControl ItemsSource="{Binding Docks}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
Height="34"
BorderThickness="1"
Tag="{Binding Self}"
>
<TextBlock
Text="{Binding Caption}"
RenderTransformOrigin="0.5,0.5"
Width="{Binding DockWidth}"
TextAlignment="Center"
VerticalAlignment="Center"
TextWrapping="Wrap"
>
</TextBlock>
<Border.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform X="{Binding Position.Item1}" Y="{Binding Position.Item2}"/>
</TransformGroup>
</Border.RenderTransform>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Does anybody have an idea what I can do? I'm not bound to a canvas. If there is another good way to do the Job, just let me know.
Viewbox should work. For example this will resize correctly (if you have jpeg with 96 dpi, if no, I think you'll have to recalculate canvas sizes):
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Viewbox Stretch="Fill"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Canvas Width="{Binding ElementName=img, Path=ActualWidth}"
Height="{Binding ElementName=img, Path=ActualHeight}">
<Image Name="img"
Source="pack://siteoforigin:,,,/images/img1.jpg" />
<Canvas Canvas.Left="100"
Canvas.Top="50">
<Rectangle Width="50"
Height="100"
Fill="Beige" />
<TextBlock Text="AAA" />
</Canvas>
</Canvas>
</Viewbox>
</Grid>

How can i make sure each items's margin inside a wrap panel is the same?

I have an ItemsControl which has a WrapPanel as its ItemsPanelTemplate. I am trying to organise a collection of buttons so that each button has the same margin on its left and right side. Here is the code I have so far:
<ScrollViewer HorizontalAlignment="Stretch" VerticalScrollBarVisibility="Auto" CanContentScroll="True">
<ItemsControl HorizontalAlignment="Stretch" ItemsSource="{Binding SystemData.PlayersList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="Gray" Margin="5">
<Button Width="180"
Style="{Styles:MyStyleRef ResourceKey=BrowserItemStyle}">
<DockPanel LastChildFill="True">
<Image Source="{Binding Icon}" Style="{Styles:MyStyleRef ResourceKey=DriveImageStyle}"/>
<Label HorizontalAlignment="Left" Content="{Binding Name}" Style="{Styles:MyStyleRef ResourceKey=DriveLabelStyle}"/>
</DockPanel>
</Button>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
And here is how it looks:
When i set the WrapPanel's HorizontalAlignment to 'Center' I get closer to the result i want.
I would like each Item to have the same margin either side of it so that it creates a uniform grid of controls - Is this possible?
Regards, Tim.
Ok got it solved... I completely over looked the uniform grid control.
Here is my code now:
<ScrollViewer HorizontalAlignment="Stretch" VerticalScrollBarVisibility="Auto" CanContentScroll="True">
<ItemsControl HorizontalAlignment="Stretch" VerticalAlignment="Top" ItemsSource="{Binding SystemData.PlayersList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="6"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="Gray" Margin="5" Height="40">
<Button
Style="{Styles:MyStyleRef ResourceKey=BrowserItemStyle}">
<DockPanel LastChildFill="True">
<Image Source="{Binding Icon}" Style="{Styles:MyStyleRef ResourceKey=DriveImageStyle}"/>
<Label HorizontalAlignment="Left" Content="{Binding Name}" Style="{Styles:MyStyleRef ResourceKey=DriveLabelStyle}"/>
</DockPanel>
</Button>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>

Need a Non-Scrolling Grid on top of a Canvas inside of a ScrollViewer in WPF

*The code below is fine, the issue was elsewhere - leaving here incase others need the info.
I am creating a simulator that has a large scrollable-zoomable Canvas. I need to be to place a StackPanel (or whatever) on top of this canvas, but it should not scroll or zoom. This is what I've got:
<Grid>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Label Content="{Binding Date, StringFormat=Date: {0}}"/>
<Label Content="{Binding CO2Level}"/>
<Label Content="{Binding O2Level}"/>
<Label Content="{Binding H2OLevels}"/>
<Label Content="{Binding TempC}"/>
<Label Content="{Binding TempF}"/>
<Label Content="{Binding SolarLevels}"/>
</StackPanel>
<ScrollViewer Name="scrollViewer" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible">
<Grid Name="grid" Width="65535" Height="65535" RenderTransformOrigin="0.5, 0.5">
<Grid.LayoutTransform>
<TransformGroup>
<ScaleTransform x:Name="scaleTransform"/>
</TransformGroup>
</Grid.LayoutTransform>
<Canvas x:Name="canvas" HorizontalAlignment="Center" VerticalAlignment="Center"
Width="0" Height="0" RenderTransform="1 0 0 -1 0 0">
<!-- Setup automatic viewing of Lifeforms -->
<ItemsControl ItemsSource="{Binding Entities}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Data="{Binding Geometry}"
Fill="{Binding Fill}"
StrokeThickness="0"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</Grid>
</ScrollViewer>
</Grid>
However it is not working, the lebels do not show up at all and I cannot figure out how to do this. Any ideas?

Overlapping Items in WPF ItemsControl

I am having a problem with DataTemplates used in a WPF ItemsControl. I want to be able to "bring forward" any of the items in the list so that they are on top of everything else in the list, but haven't had any luck.
Here's a simple example that illustrates the problem. What I want to see is achieved in the example using a WrapPanel and a bunch of colored blocks:
The first block overlaps the second. Good. When I try to do the same thing with the ItemsControl, the first block falls underneath the second, despite its ZIndex:
How can I make the first block overlap all the others in the ItemsControl?
From my understanding of the ZIndex property, I am assuming that I need to set the ZIndex higher up in the Visual Tree than the Border inside of the DataTemplate, but don't know how or where to do that.
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:local="clr-namespace:WpfApplication1"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:MainViewModel x:Key="mainViewModel" />
<DataTemplate DataType="{x:Type local:FireFighter}">
<Border Background="Pink" Padding="8" Panel.ZIndex="10" Margin="4">
<Border.RenderTransform>
<TranslateTransform X="18" Y="4"/>
</Border.RenderTransform>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}"/>
</StackPanel>
</Border>
</DataTemplate>
<DataTemplate DataType="{x:Type local:PoliceOfficer}">
<Border Background="Orange" Padding="8" Margin="4">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}"/>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ItemsControl DataContext="{StaticResource mainViewModel}"
ItemsSource="{Binding People}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<WrapPanel Margin="0,10,0,0">
<Border Background="Blue" Width="30" Height="30" Margin="4" Panel.ZIndex="10">
<Border.RenderTransform>
<TranslateTransform X="18" Y="4"/>
</Border.RenderTransform>
</Border>
<Border Background="Green" Width="30" Height="30" Margin="4"/>
<Border Background="Green" Width="30" Height="30" Margin="4"/>
</WrapPanel>
</StackPanel>
I found an answer that will work for my situation right here:
https://stackoverflow.com/a/9687758/4912801
It turns out that setting the Panel.ZIndex property wasn't working because WPF inserts a ContentPresenter element for each item in the ItemsControl. In my application, I was trying to get whatever element the mouse is hovering over to come to the front, so applying a style to all ContentPresenters in the ItemsControl allowed me to bring them to the front when the mouse was over them.
I was looking for something similar and this is what did it for me. Use a UniformGrid as your ItemsPanelTemplate, then change the margins on your DataTemplate to overlap as much as you want.
<Grid Grid.Row="1"
Grid.ColumnSpan="2">
<Grid.Resources>
<local:PointsToPathConverter x:Key="pointsToPathConverter"/>
</Grid.Resources>
<Image
Source="{Binding Bmp}"
Stretch="Fill"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" />
<ItemsControl ItemsSource="{Binding AllTraceLines}"
VerticalAlignment="Stretch"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Margin="0,-20,0,-20" Data="{Binding Converter={StaticResource pointsToPathConverter}}" Stroke="Red" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Stretch="Fill"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>

WPF ScrollViewer scroll-bars not changing size

I am not a WPF expert so please excuse my inappropriate use of terms. I have a ScrollViewer where I am displaying a captured image. And I have a slider with which I am zooming the image in and out. Zooming works fine, but the scrollbars are not changing their size. Hence when the image goes beyond the boundaries, I cannot scroll and view it. As if the scrollbars become useless because they haven't changed their size. Here is my XAML:
The Slider:
<Slider DockPanel.Dock="Right" Width="100" VerticalAlignment="Center" Minimum="0.2" Maximum="5"
Interval="1" Value="{Binding ScaleFactor}"/>
The rest of XAML:
<Border BorderThickness="1" BorderBrush="Black" Grid.Row="1">
<Grid>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
CanContentScroll="True">
<ItemsControl ItemsSource="{Binding ItemCollection}" Margin="0"
Width="{Binding Root.Boundary.Width}" Height="{Binding Root.Boundary.Height}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas>
<Canvas.LayoutTransform>
<ScaleTransform ScaleX="{Binding ScaleFactor}"
ScaleY="{Binding ScaleFactor}"CenterX="0" CenterY="0"/>
</Canvas.LayoutTransform>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Image x:Name="capturedImage"
Source="{Binding Image}"
Width="{Binding Boundary.Width}"
Height="{Binding Boundary.Height}"/>
<Path x:Name="captureContour"
Data="{Binding Outline}"
Stroke="Black" StrokeThickness="4" Opacity="0.5"
StrokeLineJoin="Round">
<Path.LayoutTransform>
<ScaleTransform ScaleX="{Binding OutlineScale.X}"
ScaleY="{Binding OutlineScale.Y}" CenterX="0"CenterY="0"/>
</Path.LayoutTransform>
</Path>
</Grid>
<DataTemplate.Triggers>
<Trigger SourceName="capturedImage" Property="IsMouseOver"
Value="True">
<Setter TargetName="captureContour" Property="Stroke"
Value="Blue"/>
<Setter TargetName="captureContour" Property="BitmapEffect">
<Setter.Value>
<DropShadowBitmapEffect/>
</Setter.Value>
</Setter>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Border>
The issue is due to the Canvas used as ItemsPanel. as Canvas does not expand or collapse with the size of it's children so ScrollViewer does not detect the change.
As a quick solution change the ItemsPanel to Grid. Since your example does not seems to be using Canvas properties i.e. Canvas.Left or Canvas.Top, this change may not make any difference in the appearance.
example
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid HorizontalAlignment="Left" VerticalAlignment="Top">
<Grid.LayoutTransform>
<ScaleTransform ScaleX="{Binding ScaleFactor}"
ScaleY="{Binding ScaleFactor}"
CenterX="0"
CenterY="0" />
</Grid.LayoutTransform>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
make sure to have HorizontalAlignment="Left" VerticalAlignment="Top" in grid otherwise it may appear weird when you zoom.
give it a try and see if this is what you are looking for.
OK guys, here is what needs to be done in order to make it work:
<ItemsControl ItemsSource="{Binding ItemCollection}" Margin="0" Width="{Binding CanvasWidth}"
Height="{Binding CanvasHeight}"/>

Categories