I have encounteres a very weird issue with drawing shapes on a canvas in WPF.
<DockPanel Grid.Row="3">
<Canvas Name="BottomCanvas" Margin="15" Background="Yellow">
<Canvas Name="TransparentCanvas" Background="Transparent"
MouseDown="TransparentCanvas_MouseDown"
MouseUp="TransparentCanvas_MouseUp"
MouseMove="TransparentCanvas_MouseMove"
Width="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Canvas}},Path=ActualWidth}"
Height="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Canvas}},Path=ActualHeight}">
</Canvas>
</Canvas>
</DockPanel>
Above there is my canvas defined.
Below there is a simple example of drawing an elipse; I have no idea why this has no effect.
Ellipse myEllipse = new Ellipse();
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
mySolidColorBrush.Color = Color.FromArgb(0, 0, 255, 0);
myEllipse.Fill = mySolidColorBrush;
myEllipse.Width = myEllipse.Height = 100;
Canvas.SetTop(myEllipse, 15);
Canvas.SetLeft(myEllipse, 15); ;
bottomCanvas.Children.Add(myEllipse);
Can anyone see what is wrong?
EDIT:
I'm sorry. As you can see in XAML canvas is named 'BottomCanvas' and in code I've used bottomCanvas which is a property added to a MainWindow I don't know what for. That was the deal.
You mean the Ellipse is not visible? It's just because of the transparent fill of SolidColorBrush. The Alpha value of the Color should not be zero, which means invisible. If so, just change the Alpha value of the Color:
mySolidColorBrush.Color = Color.FromArgb(255, 0, 255, 0);
Related
I have a struggle, I don't know how to set the background and size of a canvas with respect to MVVM, currently, I handle this only in the View part, and work ok, over this image (canvas) I want to draw some rectangle, but I want to be able to do everything according to MVVM pattern.
var bmp = new BitmapImage(new Uri(filename, UriKind.Relative));
ImageBrush brush = new ImageBrush();
brush.ImageSource = bmp;
canvas.Width = bmp.PixelWidth;
canvas.Height = bmp.PixelHeight;
canvas.Background = brush;
canvas.SnapsToDevicePixels = true;
The view model could expose a property with e.g. the image file path
public string ImagePath { get; set; }
to which you would bind like this
<Canvas Width="{Binding Background.ImageSource.PixelWidth,
RelativeSource={RelativeSource Self}}"
Height="{Binding Background.ImageSource.PixelHeight,
RelativeSource={RelativeSource Self}}"
SnapsToDevicePixels="True">
<Canvas.Background>
<ImageBrush ImageSource="{Binding ImagePath}"/>
</Canvas.Background>
</Canvas>
The conversion from string to ImageSource would automatically be performed by an ImageSourceConverter instance in WPF.
The Bindings would be simpler when the view model exposes a property of type BitmapSource:
public BitmapSource Image { get; set; }
XAML:
<Canvas Width="{Binding Image.PixelWidth}"
Height="{Binding Image.PixelHeight}"
SnapsToDevicePixels="True">
<Canvas.Background>
<ImageBrush ImageSource="{Binding Image}"/>
</Canvas.Background>
</Canvas>
I want to build a small image and text editor.
I have two Images in a UserControl.
On the left side is the editor and looks momentarily (thanks to some try and error) a little bit overstated, but it works nonetheless. (I will clean up the code later)
<Canvas x:Name="EditCanvas" Grid.Column="0"
Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
MaxWidth="426"
MouseLeftButtonDown="EditCanvas_MouseLeftButtonDown">
<Canvas.Background>
<VisualBrush TileMode="Tile" Viewport="0, 0, 1, 1" Stretch="{Binding SelectedStretch}">
<VisualBrush.Visual>
<Image x:Name="EditorImage"
Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
Source="{Binding FileImagePath}" Margin="1"/>
</VisualBrush.Visual>
</VisualBrush>
</Canvas.Background>
</Canvas>
Here I bind the Image to a UriSource. The stretching will change the Image itself, not the underlying source. Now I want to crop this image 'as it is'! So, when it is set to Stretch.UniformToFill or Stretch.Fill (for example) I want the cropped image to look exactly as it is shown in the left area. That means, the cropped image can be cropped on the bottom, the right, not at all or something like this. The formula to calculate this by myself would be a little bit greater.
Now I wonder if there isn't a better way. I have to draw text later on to this cropped image, so I cannot simply convert it to a DrawingContext, DrawingVisual, and so on and draw text in it, because that would also stretch the text later on the right side.
And if possible it would be nice to get a fast method, that can be redrawn a few times per second without killing the GPU.
For showing the preview image I use this XAML at the moment:
<Canvas Grid.Column="1"
Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
MaxWidth="426">
<Canvas.Background>
<VisualBrush TileMode="Tile"
Viewport="0, 0, 1, 1"
Stretch="{Binding SelectedStretch}">
<VisualBrush.Visual>
<Image Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
Source="{Binding PreviewImage}"
Margin="1" x:Name="TestImage"/>
</VisualBrush.Visual>
</VisualBrush>
</Canvas.Background>
</Canvas>
<Border Grid.Column="1"
BorderThickness="1" BorderBrush="Black"
Height="{Binding AreaHeight}" Width="{Binding AreaWidth}" />
And this code behind to calculate the image and fill it with the desired text (even if it is faulted at this moment):
BitmapSource imageSource = EditorImage.Source as BitmapSource;
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawImage(imageSource, new Rect(0, 0, imageSource.PixelWidth, imageSource.PixelHeight));
List<TextPosition> textPositions = GetTextPositions();
if (textPositions.Any())
{
textPositions.ForEach(x =>
drawingContext.DrawText(new FormattedText(x.Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
new Typeface(x.FontFamily, x.Style, x.Weight, FontStretch), x.FontSize, new SolidColorBrush(x.ForegroundColor),
VisualTreeHelper.GetDpi(drawingVisual).PixelsPerDip), x.Position)
);
}
}
PreviewImage = new RenderTargetBitmap(imageSource.PixelWidth, imageSource.PixelHeight, 96, 96, PixelFormats.Pbgra32);
PreviewImage.Render(drawingVisual);
TestImage.InvalidateVisual();
Ok, it doesn't seem to be possible. So I took a detour and do it now like this (if ever someone else does have a similar problem):
// Get image from the editor
BitmapSource bitmapSource = EditorImage.Source as BitmapSource;
// Get bitmap and transform it to the stretched version
Size scaleFactor = ComputeScaleFactor(new Size(AreaWidth, AreaHeight), new Size(bitmapSource.PixelWidth, bitmapSource.PixelHeight), SelectedStretch, StretchDirection.Both);
Size newCompleteSize = new Size(bitmapSource.PixelWidth * scaleFactor.Width, bitmapSource.PixelHeight * scaleFactor.Height);
Size renderSize = new Size
{
Width = newCompleteSize.Width > AreaWidth ? AreaWidth : newCompleteSize.Width,
Height = newCompleteSize.Height > AreaHeight ? AreaHeight : newCompleteSize.Height
};
Point imagePosition = GetImagePosition(renderSize);
BitmapImage bitmapImage = new BitmapImage();
using (MemoryStream memoryStream = new MemoryStream())
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
encoder.Save(memoryStream);
memoryStream.Position = 0;
bitmapImage.BeginInit();
bitmapImage.StreamSource = memoryStream;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.DecodePixelWidth = (int)Math.Round(newCompleteSize.Width, 0, MidpointRounding.AwayFromZero);
bitmapImage.DecodePixelHeight = (int)Math.Round(newCompleteSize.Height, 0, MidpointRounding.AwayFromZero);
bitmapImage.EndInit();
}
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
// Draw the copy of the image
drawingContext.DrawImage(bitmapImage, new Rect(imagePosition, new Size(bitmapImage.PixelWidth, bitmapImage.PixelHeight)));
// Check if text has to be drawn
List<TextPosition> textPositions = GetTextPositions();
if (textPositions.Any())
{
textPositions.ForEach(x =>
{
FormattedText text = new FormattedText(x.ReplacedText, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
new Typeface(x.FontFamily, x.Style, x.Weight, FontStretch), x.FontSize, new SolidColorBrush(x.ForegroundColor),
VisualTreeHelper.GetDpi(drawingVisual).PixelsPerDip)
{
TextAlignment = x.Alignment
};
TextOptions.SetTextRenderingMode(this, TextRenderingMode.Aliased);
TextOptions.SetTextFormattingMode(this, TextFormattingMode.Display);
drawingContext.DrawText(text, new Point(x.Position.X, x.Position.Y));
});
}
}
PreviewImage = new RenderTargetBitmap(
(int)Math.Round(AreaWidth, 0, MidpointRounding.AwayFromZero),
(int)Math.Round(AreaHeight, 0, MidpointRounding.AwayFromZero),
96, 96, PixelFormats.Pbgra32);
PreviewImage.Render(drawingVisual);
And in the XAML I use two images. The first one stretched, the other one not:
<Image x:Name="EditorImage"
HorizontalAlignment="{Binding AlignX}" VerticalAlignment="{Binding AlignY}"
MaxHeight="426" MaxWidth="426" Margin="1"
Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
Source="{Binding FileImagePath}" Stretch="{Binding SelectedStretch}" />
<Image Grid.Column="1"
VerticalAlignment="Center" HorizontalAlignment="Center"
Source="{Binding PreviewImage}"
Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
Stretch="None"/>
I'm using the code below to draw text over a rectangle in a WPF canvas but it seems to stretch/squash the text and sometimes the back colour does not fill the entire box.
I'm looking for a way to make sure the box is always filled and the text is clear. Probably some form of dynamic font sizing?
Thanks.
Rectangle elip = new Rectangle();
elip.Height = 6;
elip.Width = 6;
Brush brush = new SolidColorBrush(Color.FromRgb(n.Value.R,
n.Value.G, n.Value.B));
Label TB = new Label();
TB.HorizontalAlignment = HorizontalAlignment.Stretch;
TB.Margin = new Thickness(0, 0, 0, 0);
TB.Background = brush;
TB.FontSize = 12;
TB.HorizontalContentAlignment = HorizontalAlignment.Center;
TB.Content = n.Value.Stations[0].TrackId;
BitmapCacheBrush bcb = new BitmapCacheBrush(TB);
elip.Fill = bcb;
elip.Stroke = Brushes.Black;
elip.StrokeThickness = 0.5;
elip.MouseDown += ElipOnMouseDown;
Canvas.SetTop(elip, n.Value.Y - elip.Width / 2);
Canvas.SetLeft(elip, n.Value.X - elip.Height / 2);
cMain.Children.Add(elip);
You can stretch the text using a viewbox.
I recommend using a usercontrol to encapsulate all your markup for things like this. You would have to have many thousands of these things before the overhead of a usercontrol vs building rectangles and whatnot would be significant.
I built this usercontrol:
Height="18" Width="24">
<Border BorderBrush="Black"
BorderThickness="1"
Background="Magenta"
>
<Viewbox Stretch="Uniform">
<TextBlock Text="{Binding Tag, RelativeSource={RelativeSource AncestorType=UserControl}}"
TextAlignment="Center"
Margin="2"
/>
</Viewbox>
</Border>
</UserControl>
Might not be precisely what you want in terms of width, height or whatever.
You could instantiate one of these, set it's Tag, canvas top and left and add it to the canvas.
Here's the equivalent markup I used to prove it:
Title="MainWindow" >
<Grid>
<Canvas>
<local:StationView Canvas.Left="20"
Canvas.Top="100"
Tag="16B"/>
</Canvas>
</Grid>
</Window>
I have a problem when I'm trying to draw a line with the MVVM-Pattern in WPF.
I followed this example: Drawing line on a canvas in WPF MVVM doesn't work
The Problem I have, is that I want to draw lines on a video control that is already implemented, which I can't use as ItemsPanelTemplate.
So my code currently looks like this:
xaml
<ContentControl>
<views:ExtendedMediaElement x:Name="MediaPlayer" Grid.Row="0" ScrubbingEnabled="True" LoadedBehavior="Manual" UnloadedBehavior="Stop"
MediaLength="{Binding MediaLength, Mode=OneWayToSource}"
MediaPosition="{Binding MediaPosition, Mode=TwoWay}"
EndPosition="{Binding EndPosition, Mode=OneWay}"/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<cal:ActionMessage MethodName="MouseDown">
<cal:Parameter Value="$eventArgs"/>
<cal:Parameter Value="{Binding ElementName=ItemControl}"/>
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</ContentControl>
<ItemsControl x:Name="ItemControl" ItemsSource="{Binding Path=Lines}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding X1}" Y1="{Binding Y1}" X2="{Binding X2}" Y2="{Binding Y2}" Stroke="{Binding Stroke}" StrokeThickness="{Binding StrokeThickness}"></Line>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
ViewModel
private ObservableCollection<Line> _lines;
public ObservableCollection<Line> Lines
{
get { return _lines; }
set
{
if (_lines != value)
{
_lines = value;
NotifyOfPropertyChange("Lines");
}
}
}
public void MouseDown(MouseButtonEventArgs e, System.Windows.Controls.ItemsControl lineArea)
{
System.Windows.Point p = e.GetPosition(lineArea);
if (!isFirstPointSet)
{
firstClickPoint = p;
isFirstPointSet = true;
return;
}
Line l = new Line();
l.X1 = firstClickPoint.X;
l.Y1 = firstClickPoint.Y;
l.X2 = p.X;
l.Y2 = p.Y;
l.Stroke = System.Windows.Media.Brushes.Black;
l.StrokeThickness = 4;
Lines.Add(l);
firstClickPoint = p;
}
Now the first line I draw is always perfectly placed on the Click-Coordinates, but the second line has an offset on the X-Coordinate. The thrid line is often not even visible any more.
Thanks in advance for your help!
Niko
Take a look at the below example, each item in an ItemsControl are at different position. If the first line is drawn on canvas 32, the second line will be drawn on canvas 33. They are not drawn on the same, big canvas as you might expected.
You need to set the ItemsPanel property just like the answer in the link you provided.
How to make the green rectangle to be visible only inside the blue one? The blue is a border of a grid. I want to cut off everything that is not inside this grid. Notice that the green rectangle will be moving.
As there is no ClipToBounds property in Silverlight, you would have to set the Clip property to a RectangleGeometry.
When the Grid's size is fixed, you may simply set a fixed size rectangle:
<Grid>
<Grid.Clip>
<RectangleGeometry Rect="0,0,400,600"/>
</Grid.Clip>
...
</Grid>
When the Grid's size can change, you may set the Clip property in a SizeChanged handler:
<Grid SizeChanged="GridSizeChanged">
...
</Grid>
The handler code:
private void GridSizeChanged(object sender, SizeChangedEventArgs e)
{
((UIElement)sender).Clip =
new RectangleGeometry
{
Rect = new Rect(0, 0, e.NewSize.Width, e.NewSize.Height)
};
}
<Grid Width="200" Height="100">
<Grid.Clip>
<RectangleGeometry Rect="0, 0, 200, 100"/>
</Grid.Clip>
source: http://www.scottlogic.co.uk/blog/colin/2009/05/silverlight-cliptobounds-can-i-clip-it-yes-you-can/