How to custom-paint a GridSplitter - c#

I'm migrating form WinForms to WPF, and I need to custom-draw a gridsplitter, as follows. This is my WinForms implementation:
In WinForms it's easy to implement. I inherited from the Splitter class and just override the OnPaint() method.
Now, I'm trying to figure out how to proceed. I don't know how to use a control template in XAML because I need to re-paint the shapes as the user moves the scroll. Seems that I need to write code behind, but I don't know how to proceed.
How might I proceed? A simple example, such as drawing a line from (0,0) to (gridsplitter.right, gridsplitter.bottom) would help.

Finally, I created a Canvas-derived class to perform the rendering:
public class DiffSplitterCanvas : Canvas
{
public DiffSplitterCanvas()
{
this.SnapsToDevicePixels = true;
}
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
// draw your stuff here
}
}
And referenced it as a control template from the XAML code:
<GridSplitter Grid.Column="1" Width="50" HorizontalAlignment="Stretch">
<GridSplitter.Template>
<ControlTemplate TargetType="{x:Type GridSplitter}">
<custom:DiffSplitterCanvas/>
</ControlTemplate>
</GridSplitter.Template>
</GridSplitter>

You could try painting to a WriteableBitmap and using that as the background for the splitter. That might involve overwriting the splitter's template to get things exactly right. I'm not sure how performant that will be with scrolling though.
WriteableBitmap provides only lowlevel access to pixels, if you want higher level functions like drawing lines and circles you may want to look in to a library like WriteableBitmapEx that extends the class with GDI like drawing commands.
Code to set a GridSplitter's background:
<GridSplitter x:Name="gridSplitterTreeNodes" Width="5" BorderThickness="1,0"
Cursor="SizeWE" RenderTransformOrigin="-1.2,0.507" ShowsPreview="True"
Style="{DynamicResource GridSplitterStyle1}">
<GridSplitter.Background>
<ImageBrush ImageSource="Images\gripDots.png" TileMode="FlipXY"
Stretch="UniformToFill"/>
</GridSplitter.Background>
</GridSplitter>
You would want to set the ImageBrush to your WritableBitmap.
Drawing a simpleLine using WriteableBitmapEx:
writeableBmp.DrawLine(1, 2, 30, 40, Colors.Green);

Related

Is it possible to control Composition XAML ElementVisual Clipping?

I am using UWP and working with the Composition API to programmatically scale child text visuals that are nested in a typical XAML hierarchy. The textblocks in our app are contained in things like borders and a number of those borders are items contained in a GridView.
In many of the scenarios I am experiencing clipping of the associated text visual as it scales to be larger than some of the XAML containers that host the elements and I would like the visual to not get clipped as it scales to be larger than its parent.
Here is a barebone example that demonstrates some of the problems I am seeing…
My test app starts as a blank UWP app and the root grid of my page contains the following Gridview:
<GridView >
<GridViewItem>
<Border PointerPressed="Border_PointerPressed" CornerRadius="5" Width="125" Height="125">
<Grid>
<TextBlock Text="Content String 1" />
</Grid>
</Border>
</GridViewItem>
<GridViewItem>
<Border PointerPressed="Border_PointerPressed" Width="125" Height="125">
<Grid>
<TextBlock Text="Content String 2" />
</Grid>
</Border>
</GridViewItem>
<GridViewItem>
<Border PointerPressed="Border_PointerPressed" Width="125" Height="125">
<Grid>
<TextBlock Text="Content String 3"/>
</Grid>
</Border>
</GridViewItem>
</GridView>
The codebehind file contains the following additional using statements, a variable declaration, variable initialization in page constructor and this event handler:
using System.Numerics;
using Windows.UI.Composition;
using Windows.UI.Xaml.Hosting;
Compositor compositor;
public MainPage()
{
this.InitializeComponent();
compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
}
private void Border_PointerPressed(object sender, PointerRoutedEventArgs e)
{
var content = VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(sender as FrameworkElement, 0), 0);
var visual = ElementCompositionPreview.GetElementVisual(content as FrameworkElement);
var animation = compositor.CreateVector3KeyFrameAnimation();
animation.InsertKeyFrame(0f, new Vector3(1.0f, 1.0f, 0.0f));
animation.InsertKeyFrame(0.5f, new Vector3(3.0f, 3.0f, 0.0f));
animation.InsertKeyFrame(1f, new Vector3(1.0f, 1.0f, 0.0f));
animation.Duration = TimeSpan.FromMilliseconds(5000);
visual.StartAnimation(nameof(visual.Scale), animation);
}
When you run the app and click on each of the strings you should initially notice that the first string behaves differently than the other two string.
The first string gets cropped at the Border's bounding box whereas the other two strings do not.
Also note that the other two strings appear to scale past the bounds of last item and out into the page, but that turns out to probably be due to the gridview autosizing to fill the page.
The difference between the first string and the other two is that the border has a corner radius property set on it. We use cornerradius setting in our application, so it would be nice to know if there is a way to override or control this behavior so that it doesn't clip the visual as it scales.
The other behavior that is causing us problems is that at the GridView bounds is another boundary that the visual is clipping at as it scales. If you set any property (like HorizontalAlignment="Center") on the Gridview that causes it to size itself to only be as big as it needs to be, then the visual gets cropped at the controls boundaries.
Is there anything within the Compositional API that allows me to prevent or influence this clipping behavior?

Adding collection of Images in Xaml

I would like to paint Images to the XAML page in Windows Store App.
The main goal is that:
Adding Images (e.g. flower leaf) to a circle on the center like that:
I have a simple solution to that, but its is very redundant.
<Image Height="200" Width="200" Source="{Binding ActualImage.NormalUri}">
<Image.RenderTransform>
<RotateTransform Angle="12"></RotateTransform>
</Image.RenderTransform>
</Image>
... // And 28 other like these
<Image Height="200" Width="200" Source="{Binding ActualImage.NormalUri}">
<Image.RenderTransform>
<RotateTransform Angle="360"></RotateTransform>
</Image.RenderTransform>
</Image>
How can I do that with a binding of a Image collection? What XAML control should I use?
Use a custom class which inherits from ItemsControl. You can then override the necessary functions, such as ones to determine the angle to rotate between each item. I think it's likely you'll want to use this PrepareContainerForItemOverride for this.
One thing to note that you will have to do is to define a new ItemsPanel. The default is a StackPanel, which will not work. You'll likely want to use something like a Canvas, which allows you to explicitly position items in it.
The Solution of Nate Diamond is much more nicer and better, but I solved it from code-behind for earn the easier way:
foreach (Petal petalObject in MainPageViewModel.Petals)
{
var petalImage = new Image
{
Height = petalObject.Height,
Width = petalObject.Width,
RenderTransform = new RotateTransform() {Angle = petalObject.Angle},
Source = new BitmapImage(new Uri(petalObject.NormalUri)),
};
PetalsGrid.Children.Add(petalImage);
}

Is it possible to set a border to overlay (like in photoshop)

I am having a quite exotic question:
I am currently working on a "borderless" WPF Application. I successfully removed the standard windows window controls and added my own. Now I wanted to add a border to the programm. But I don't want the border to have specifc color, I want it to be in some kind of overlay mode like when you set a layer in photoshop to overlay. You can see what I mean in an example:
I really want it to look like on the left. But unfortunately I really have no idea how.
Basically what the overlay does is that it takes the background color and it makes it a bit darker.
Do you have any suggestions what I could do?
Edit: Here is a better picture of the effect
Photoshop Mookup2 http://img.xrmb2.net/images/243147.png
Edit2 : So, as a short explanation because it may not be as clear as I have hoped: I am not talking about a semi-transparency. I know how to do that. The right box in both images uses that. It has a black-semitransparent look. While on the left I set the border to "overlay" mode. As you can see it gets darker but also a bit stronger in color.
How about using the Transparent brush?:
<Border BorderBrush="Transparent" BorderThickness="5">
...
</Border>
UPDATE >>>
Oh, hold on... I see what you want... try this instead with a semi opaque `brush:
<Border BorderBrush="3F000000" BorderThickness="5">
...
</Border>
You can adjust the darkness of the Border, by changing the first two hexadecimal characters: 3F.
UPDATE >>>
I believe that this is the correct solution. As I said last:
You can adjust the darkness of the Border, by changing the first two hexadecimal characters
So just try different values until you get the right balance of opacity. Start with hardly visible black and then increase the opacity until you get it right:
<Border BorderBrush="08000000" BorderThickness="5">
...
</Border>
...
<Border BorderBrush="0F000000" BorderThickness="5">
...
</Border>
...
<Border BorderBrush="1F000000" BorderThickness="5">
...
</Border>
...
<Border BorderBrush="2F000000" BorderThickness="5">
...
</Border>
If none of these values look quite right still, then use a semi-opaque white instead:
<Border BorderBrush="2FFFFFFF" BorderThickness="5">
...
</Border>

Draw text on a shape in a wpf

Some of you maybe find this question dull but I am still not deeply accustomed to wpf drawing. I want to add formatted text on a Rectangle which moves around on a canvas and I have got a hint to override the UIElement.OnRender method. However I do not know if I should override the canvas class or the Shape class. In any correct case, to what refers the drawingContext parameter of the method as described in the example: http://msdn.microsoft.com/en-us/library/bb613560.aspx#FormattedText_Object ?
Is the text ultimately assigned to the shape or is it a visual temporary effect that cannot move along with the shape on the canvas?
Is there any further effective means of drawing text on a shape?
You can draw Text on top of a Rectangle by placing both controls in a parent container that allows controls to overlap, such as a Grid or a Canvas
<Grid>
<Rectangle Fill="Red" Stroke="Black"
HorizontalAlignement="Stretch" VerticalAlignment="Stretch" />
<Label Content="Test"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
You can then apply whatever formatting you want to the Label, the Rectangle, and you can move the group around by setting the positioning of the Grid
Rachel's answer is correct, although you can extend it a bit, have some UserControl defined as:
And in the codebehind define 1. Label:String DependencyProperty, Shape:UIElement DependencyProperty.
Handle the Shape's change event and call:
private void UpdateShape()
{
grdShapeContainer.Children.Clear();
if(this.Shape != null)
{
grdShapeContainer.Children.Add(this.Shape);
}
}
This way you will be able to make things dynamic.
Regards,
Artak
You might also want to look into ZIndex property which can be set on objects like Grid (<Rectangle Background="Black" Grid.ZIndex = 99 /> for instance would put it overtop other items) which useful for making things like "loading" screens.

C# Winforms-WPF interop

Currently I have a Winforms app which relies on transpareny effects. However this is proving to be an absolute pain in the preverial behind! Winforms as Im learning doesn't deal with transparency particularly well.
I was wondering whether or not this would be any easier using WPF components for the transparency bit and winforms for the rest (note althought Id like to move the whole app over to WPF this just isn't feasable!). I know next to nothing about WPF, hence the reason Im here! What I was considereing was :
1) Host a WPF component within a Winforms User Control e.g. Example of WPF Control:
<UserControl x:Class="WindowsFormsApplication1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid>
<Rectangle Name="rectangle1" Stroke="White" Fill="Black" RadiusX="10" RadiusY="10" Opacity="0.7" />
<Rectangle Margin="57,101,43,99" Name="dialog" Stroke="Gray" Fill="White" RadiusX="10" RadiusY="10" />
</Grid>
</UserControl>
2) Host a Winforms user control (content) within the white rectangle (dialog) of the WPF control.
3) allow the content (Winforms user control) to call code on the parent of the WPF-Control.
First things first...
Is this a reasonable thing to do or am I barking up the wrong tree?
Can this be achieved in an easier fashion?
Can anyone help me here? (Sample code would be gratefully received!)
Finally ... are there any online resources that can help me a) learn WPF and b) become more self-sufficient?
It certainly is possible, and I think you are right that it would be the easiest way to work with transparency.
I haven't tried it myself, but according to this article on CodeProject, it should be quite simple. You should use the ElementHost control to host your WPF content.
Hosting WPF in a WinForms control is a supported scenario, a feature built into the framework. So there should be no problems in doing so. There is also a WPF component for going the other way, hosting WinForms in a WPF app.
Here is the solution I used to solve the problem at Hand. This solution relies on the overlaying Control to render its Parent as a bitmap image. This then gets painted as the background of the overlaying control.
public class OverlayingControl : UserControl
{
/// <summary>
/// Overrides the c# standard Paint Background to allow the custom background to be drawn
/// within the OnPaint function
/// </summary>
///
/// <param name="e">Arguements used within this function</param>
protected override void OnPaintBackground( PaintEventArgs e )
{
//Do Nothing
}
protected override void OnPaint( PaintEventArgs e )
{
// Render the Parents image to a Bitmap. NB: bitmap dimensions and Parent Bounds can be changed to achieve the desitred effect
Bitmap background = new Bitmap( Width, Height, PixelFormat.Format64bppArgb );
Parent.DrawToBitmap( background, Parent.Bounds );
// Paint background image
g.DrawImage( background, 0, 0, new RectangleF( Location, Size ), GraphicsUnit.Pixel );
// Perform any alpha-blending here by drawing any desired overlay e.g.
// g.FillRectangle( new SolidBrush( semiTransparentColor ), Bounds);
}
}
This is performed purely within the WinForms domain, however I believe it could be possible to pass this Bitmap image to a WPF control to render as required. Currently there is no provision for updating the Bitmap when the parent changes, However, it should be trivial to create a custom method that clears the bitmap and re-draws the Overlayng control. Not an elegant solution I realise... but it appears to work well enough.

Categories

Resources