ScaleTransform applying Translate effects - wpf

I want to create a zooming control, that can be operated with mouse wheel.
Scroll up -> zoom in; scroll down -> zoom out.
Additionally, I want the zoom center to be located at wherever the mouse pointer is. So that one can always zoom in on the spot, where the mouse cursor is.
Seems simple enough, yet I can't get to work.
I am using a Grid and applying ScaleTransform to it.
When I zoom in the first time, it works and zooms in that particular spot. But then, if I move the cursor to some other spot and try to zoom in a bit more, the Grid is being offset and my initial center is off.
What is causing this? How can fix this?
My code:
Class MainWindow
Dim trans As New ScaleTransform
Dim Scale As Double = 1
Private Sub DefGrid_MouseWheel(sender As Object, e As MouseWheelEventArgs) Handles DefGrid.MouseWheel
If e.Delta > 0 Then
Scale = Scale + 0.1
End If
If e.Delta < 0 Then
Scale = Scale - 0.1
End If
trans.CenterX = e.GetPosition(DefGrid).X
trans.CenterY = e.GetPosition(DefGrid).Y
trans.ScaleX = Scale
trans.ScaleY = Scale
DefGrid.RenderTransform = trans
End Sub
End Class
And the xaml:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" PreviewMouseWheel="Window_PreviewMouseWheel">
<Grid x:Name="DefGrid" HorizontalAlignment="Left" Height="291" Margin="19,10,0,0" VerticalAlignment="Top" Width="475">
<Canvas HorizontalAlignment="Left" Height="254" Margin="137,10,0,0" VerticalAlignment="Top" Width="195">
<Canvas.Background>
<ImageBrush ImageSource="TestImage.jpg"/>
</Canvas.Background>
</Canvas>
</Grid>
</Window>
There's a Canvas image on the grid, just for reference.

It's a bit more complicated unfortunately. If you don't think you'll need scrollbars, check the answers here (I couldn't find VB-specific examples, so these are C#):
Pan & Zoom Image
If you need scrollbars as well, you will need to scroll to the mouse pointer. In that case you need something like this:
http://www.codeproject.com/Articles/97871/WPF-simple-zoom-and-drag-support-in-a-ScrollViewer

Related

Programmatically position control in WPF

I am creating a karaoke program in WPF. Basically, I have a MediaElement which plays vidioes and such, and a StackPanel atop of that which I use to render stuff on top of the MediaElement.
I am trying to programmatically add a TextBlock to the StackPanel, which is going display the lyrics. The problem is that the TextBlock ends up in the top left corner no matter what I write.
Private LyricLabel As New TextBlock
Sub New(Panel As StackPanel)
With LyricLabel
.Foreground = Brushes.White
.FontFamily = New FontFamily("Verdana")
.FontSize = 20
.HorizontalAlignment = HorizontalAlignment.Stretch
.VerticalAlignment = VerticalAlignment.Bottom
End With
Panel.Children.Add(LyricLabel)
End Sub
Also, I want a ball or something to jump from word to word. Is there a easy way to get the width of each of the words + the space between them, or do I have to calculate that by myself?
It would be a better option to use a Grid and do that entirely in XAML. The Grid has the property that if many elements are in the same cell, they all overlap. So you put the MediaElement and a TextBlock together, with proper alignments and you're done:
<Grid>
<MediaElement/>
<TextBlock Text="{Binding CurrentLyric}" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0 0 0 30"/>
</Grid>
You just have to provide a property for dropping the current text to be shown for the binding to work, and it will adjust it for you.

Display a Huge image in WPF application

I wish to display a HD size 1920X1080 image in my WPF application. When I set the image source in the image control, only part of the image is displayed, size of image control.
I do not wish to auto-fit the HD image into the image control. Say the image control is size 640 X 480 then the 640X480 of HD image should be displayed. But when I pan the image then the next 640 X 480 of the image should be displayed.
I already have implemented TransformGroup
TransformGroup group = new TransformGroup();
ScaleTransform xform = new ScaleTransform();
group.Children.Add(xform);
TranslateTransform tt = new TranslateTransform();
group.Children.Add(tt);
But my problem is that entire image is not loaded initially.
If all you want is the ability to scroll your image...then use:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ScrollViewer HorizontalScrollBarVisibility="Visible">
<Image Stretch="None" Source="c:\mytestimage.png" />
</ScrollViewer>
</Grid>
</Window>
See these handy posts if you want to be more sophisticated/efficient:
WPF Image Zooming
Pan & Zoom Image
http://www.codeproject.com/Articles/85603/A-WPF-custom-control-for-zooming-and-panning
There is an alternate way to display the image. Set the max-es on the image, so it wont visually load larger than what is needed.
<Image Source="{Binding Source}"
Stretch="UniformToFill"
MaxHeight="200"
MaxWidth="400"/>
Image below shows two images loaded as is, a small one and a large one. The left side shows the large one exploding past the boundaries. The right one shows the proper shrinkage for the large one.
To keep the smaller image from oversizing (as shown on the right side) set the StretchDirection="DownOnly" to keep the smaller one from expanding.

What is the best way to draw the grid for a Dots and Boxes game? (Silverlight)

I am trying to implement a Dots and Boxes style game in Silverlight for Windows Phone 7. What is the best way to draw the dot and box grid so that I get notified when someone touches the space between two boxes? What XAML elements should I look at using?
A polygon shaped like the image below, with an overlaid line, would be your best bet.
You will set the polygon fill (shown in blue) to 1% alpha so that it is not visible, but is hit-testable (0% alpha turns off hit testing).
If you create one as a usercontrol, you can simply place them around your grid of dots with 90% rotation on the vertical ones:
The dots can be simple ellipses (turn off isHitTestVisible on these):
You can then simply turn on/off the visibility of the lines in the user controls (which are always present for hit-testing):
I suggest a canvas for the outer control to give you fine position adjustment from code, but a grid will work too if you get the margin offsets right.
Usercontrol XAML (created with Blend):
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="DotsAndBoxes.Connector"
d:DesignWidth="280" d:DesignHeight="80">
<Grid x:Name="LayoutRoot" Background="Transparent">
<Path Data="M27.706778,279.98367 L68.48111,239.30304 L266.99857,239.30304 L306.88052,278.89899 L266.99857,318.49493 L68.481102,318.49493 z"
Fill="#022E2EFB"
Stretch="Fill"
UseLayoutRounding="False"
IsHitTestVisible="True"
MouseLeftButtonDown="Path_MouseLeftButtonDown"/>
<Path Data="M0,40 L40.218182,40 L280,40" Height="5" Stretch="Fill" StrokeThickness="5" UseLayoutRounding="False" VerticalAlignment="Center" Stroke="White" IsHitTestVisible="False"/>
</Grid>
</UserControl>
Expose a "click" event on the User Control, that is called from a LeftMouseButtonDown event on the polygon and catch those click events in the high-level container:
namespace DotsAndBoxes
{
public partial class Connector : UserControl
{
public event EventHandler<System.Windows.Input.MouseButtonEventArgs> Clicked;
public Connector()
{
// Required to initialize variables
InitializeComponent();
}
private void Path_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (Clicked != null)
{
Clicked(this, e);
}
}
}
}
You could generate one of these polygons by hand as the coordinates required are quite simple.

New to WPF - What Control to Use / Getting Started?

I'm a WPF n0ob and I'm struggling with selecting the appropriate control to get the layout I want.
What I'm trying to do is draw a bunch of squares (virtual post-it notes) onto the screen. Each note is going to be a decent size (~150 pixels or so) and there could be hundreds of these notes. I want the whole thing to be scrollable so that you can resize the window however you like and the whole thing should be zoomable.
I've done this and it works.
But what I've done seems awfully wrong....
In the code, I'm dynamically creating post it notes and adding them to a giant canvas. I'm manually doing the math to determine where to place each note and how big the canvas should be. I added some labels at the top and had to go back and add a 'Y Offset' value to push all the squares down. I actually generate three different canvas controls and then add each one of them to a stack panel that is inside of a ScrollViewer. I added a scroll bar and set the the stack panel to zoom in and out as you adjust the bar.
It 'works', but I feel like I'm really not using WPF the way it's meant to be used. I tried achieving the same thing with a grid, but the grid didn't seem to want to size itself appropriately.
Can someone tell me a 'better' way to achieve the same look?
Here's my Xaml code - as you can see; there isn't much to it....
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition />
</Grid.RowDefinitions>
<Slider x:Name="ZoomSlider" Minimum="0.01" Value="1" Maximum="2" Margin="0,0,0,6" />
<ScrollViewer x:Name="MyScroller" Grid.Row="1" HorizontalScrollBarVisibility="Visible" HorizontalContentAlignment="Center" VerticalContentAlignment="Center">
<StackPanel x:Name="TicketsGrid" Background="White" HorizontalAlignment="Center">
</StackPanel>
</ScrollViewer>
</Grid>
And then here is what I'm doing in code (ugly!!!)
For Each myWorkItem As WorkItem In myWorkItems
Dim newRect As New Border
newRect.Width = TicketSizeX
newRect.Height = TicketSizeY
If myWorkItem.State.ToUpper.Contains("HOLD") Then
newRect.Background = New SolidColorBrush(Colors.Purple)
Else
newRect.Background = New SolidColorBrush(Color)
End If
newRect.CornerRadius = New System.Windows.CornerRadius(5)
newRect.BorderThickness = New System.Windows.Thickness(1)
newRect.BorderBrush = New SolidColorBrush(Colors.Black)
Dim myPanel As New StackPanel
newRect.Child = myPanel
Dim lblTitle As New Label
lblTitle.Content = myWorkItem.Id
lblTitle.FontWeight = System.Windows.FontWeights.Bold
Dim lblDesc As New TextBlock
lblDesc.Text = myWorkItem.Title
lblDesc.TextWrapping = TextWrapping.Wrap
myPanel.Children.Add(lblTitle)
myPanel.Children.Add(lblDesc)
newRect.SetValue(Canvas.LeftProperty, CType(((TicketCount Mod TicketsXPerUser) * TicketStepX) + (xOffset * TicketStepX * TicketsXPerUser), Double))
newRect.SetValue(Canvas.TopProperty, CType(((Math.Floor((TicketCount / TicketsXPerUser)) * TicketStepY)) + NameLabelHeight, Double))
myCanvas.Children.Add(newRect)
TicketCount += 1
Next
MyCanvas.Width = (TicketStepX * TicketsXPerUser) * myTFS.SharedCodeTeam.Count
MyCanvas.Height = (CType(((Math.Floor((MaxTicket / TicketsXPerUser)) + 1) * TicketStepY), Double))
TicketsGrid.Children.Add(MyCanvas)
ScrollViewer with an ItemsControl inside.
Bind the ItemsSource property of the ItemsControl to an ObservableCollection<PostIt> (where PostIt is a plain old CLR object with all the info that goes on the post it).
Add a DataTemplate to the ItemsTemplate property of the ItemsControl
Add controls to the DataTemplate and bind them directly to an instance of PostIt
Add PostIt instances to the ObservableCollection<PostIt> in your code.
The ScrollViewer handles all scrolling. That's all you need.
The ItemsControl is designed to bind against a collection. For each instance in the collection, it figures out what DataTemplate to use, creates a copy of the template, sets the root's DataContext to the instance it pulled from the collection, and adds the template to itself. It does this for each instance found in the collection.
In your codebehind, all you need to do is create a bunch of PostIts and add them to the collection. No godawful construction of UI elements like you're doing. Urgh.
If you can grasp this concept, you are a step away from understanding MVVM, the Model-View-Controller pattern in WPF. Read about it, try it out. Its a very simple way of making very complex applications with complex UI but with a minimum of code (and none of that crap you're doing currently).

Flowdocument isnt using the full width/height

I have a FlowDocument which I want to fill the entire width and height of my window. I have tried using the FlowDocumentPageViewer (no luck) and am now using a DocumentPageView. I still can't get it to dock/fill the entire space; it's just sitting in the middle, in the minimum size it can create (does it make sense?)
Here is my code:
public DocumentPageView GetPage()
{
FlowDocumentPageViewer viewer = new FlowDocumentPageViewer();
StreamReader reader = new StreamReader(location);
string data = reader.ReadToEnd();
reader.Close();
string xamlData = HtmlToXamlConverter.ConvertHtmlToXaml(data, true);
FlowDocument result = (FlowDocument)System.Windows.Markup.XamlReader.Load(new MemoryStream(System.Text.UnicodeEncoding.Default.GetBytes(xamlData)));
viewer.Document = result;
viewer.VerticalAlignment = VerticalAlignment.Center;
viewer.HorizontalAlignment = HorizontalAlignment.Center;
DocumentPageView pageView = new DocumentPageView();
pageView.VerticalAlignment = VerticalAlignment.Center;
pageView.HorizontalAlignment = HorizontalAlignment.Center;
pageView.Stretch = System.Windows.Media.Stretch.Uniform;
pageView.PageNumber = 0;
pageView.StretchDirection = StretchDirection.Both;
pageView.DocumentPaginator = ((IDocumentPaginatorSource)result).DocumentPaginator;
return pageView;
}
Please note that this code contains the combination of my two methods but only the DocumentPageView is currently used. This is the Xaml that is created from my HTML source:
<FlowDocument xml:space="preserve" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Paragraph TextAlignment="center" FontSize="22pt" FontFamily="arial">Test Paragraph</Paragraph>
<Paragraph TextAlignment="center" FontFamily="arial">Test second paragraph</Paragraph>
</FlowDocument>
If I resize the fonts the content is only resized vertically (please note that the stretch direction is set to both). Any ideas?
I had a similar problem with FlowDocumentScrollView, but this solution also seems to work with FlowDocumentPageView:
The FlowDocument is centered because it's PagePadding property is set to auto,auto,auto,auto. Setting PagePadding to 0 fixes this behavior.
<FlowDocumentScrollViewer VerticalScrollBarVisibility="Auto">
<FlowDocument PagePadding="0">
</FlowDocument>
</FlowDocumentScrollViewer>
The following lines are causing your elements to center themselves (which is not the same as stretch):
viewer.VerticalAlignment = VerticalAlignment.Center;
viewer.HorizontalAlignment = HorizontalAlignment.Center;
pageView.VerticalAlignment = VerticalAlignment.Center;
pageView.HorizontalAlignment = HorizontalAlignment.Center;
You can safely remove them, as the default alignment here is Stretch.
If you still want to center the viewer, explicitly define the page size (remember, there are 96 points in an inch, and the Margin and PageSize are set in points):
Width= 96 * 8.5
Height= 96 * 11
Could you specify where and how do you use the result of your GetPage() method? Is it xbap or desktop application?
I'm asking this, because the following document is displayed just perfectly right in Kaxaml:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<FlowDocumentPageViewer>
<FlowDocument >
<Paragraph TextAlignment="center" FontSize="22pt" FontFamily="arial">Test Paragraph</Paragraph>
<Paragraph TextAlignment="center" FontFamily="arial">Test second paragraph</Paragraph>
</FlowDocument>
</FlowDocumentPageViewer>
</Grid>
</Page>
PS: If it's a desktop application you can always find who causes problems by Snoop tool, form Pete Blois.
Update: Its a desktop application, the getpage() result is posted into a grid which docks/fills perfectly.
<Window x:Class="GreenWebPlayerWPF.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="Auto" Width="Auto" WindowStyle="None" WindowState="Maximized" WindowStartupLocation="CenterScreen" Loaded="Window_Loaded" Closing="Window_Closing">
<Grid Width="Auto" Height="Auto" Name="TransitionContainer" Background="White" Margin="0">
//here the result from GetPage() method is inserted
</Grid>
</Window>
(this comment is written from another account)
#Anvaka: What i mean by perfectly right is that the document should "dock in container" that is the fonts should resize, it should fill the container in height and width. Now that im thinking about it, that may not seem like a proper behaviour for a flowdocument.
When i put the flow document in the container it is centered in middle of parent container (so far so good). but the parent container is not filling out its parent container so when i zoom or change font size, i would like the documentPageView container to grow in width and height, but remain centered.

Resources