Helixtoolkit Rotate GeometryModel3D using mouse in all 3 axis - wpf

The goal is to measure a GeometryModel3D boundary (width, depth, height) but in some situations the model orientation is not correct so I want to allow the user to correct the orientation using the mouse with an interface similar to the camera controller (In inspect mode) in order for the user to correctly align the model.
I was able to achieve this to some extent using the RotateManipulator class but this limits to only correct the orientation in one axis. Furthermore I derived from the class and implemented my own mouse handler to change the RotateTransform3D axis but the result is not what I expect.
Imports System.Windows.Data
Imports System.Windows.Media.Media3D
Imports System
Imports System.Windows
Imports System.Windows.Input
Imports HelixToolkit
Imports HelixToolkit.Wpf
Public Class RenderRotateManipulator
Inherits HelixToolkit.Wpf.RotateManipulator
Private m_model As GeometryModel3D
Private m_transform As New RotateTransform3D
Private m_clickedPoint As Point
Public Sub New(ByRef g As GeometryModel3D)
MyBase.New
m_model = g
Me.TargetTransform = m_transform
UpdateGeometry()
End Sub
Protected Overrides Sub UpdateGeometry()
Me.Model.Geometry = m_model.Geometry
Me.Model.Material = m_model.Material
End Sub
Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs)
Dim currentPoint As Point = e.GetPosition(Me.ParentViewport)
MyBase.OnMouseMove(e)
If (Me.IsMouseCaptured) Then
Me.Model.Transform = Me.TargetTransform
If Not IsNothing(m_clickedPoint) Then
Dim delta As Point = currentPoint - m_clickedPoint
Dim angle As Double = Math.Atan2(delta.Y, delta.X)
'Console.WriteLine("Angle=" & angle * Math.PI / 180 & " Clicked point=" & m_clickedPoint.ToString & " CurrentPoint=" & currentPoint.ToString)
Me.Axis = New Vector3D(0, Math.Sin(angle), Math.Cos(angle))
End If
End If
End Sub
Protected Overrides Sub OnMouseDown(ByVal e As MouseButtonEventArgs)
MyBase.OnMouseDown(e)
m_clickedPoint = e.GetPosition(Me.ParentViewport)
End Sub
End Class
Another approach I was thinking is to use the perpective camera controller and use the difference of two camera locations as you move the camera (which looks as if the Model is rotating) and calculate the transformation from these two deltas which I can apply to the Model. But I'm not sure the Math required. I had a look at the RotateHandler.cs (Rotate camera using 'Turntable' rotation.) but a bit of direction would be greatly appreciated.

Related

Combining text wrapping and shrinking in WPF

I my WPF app I have a relatively small box of limited width, where I need to display some text which has been entered by the user. The text can realistically be expected to be between one and five words, but the words can easily be larger than the box.
If the text is too long, but contains multiple words which can be broken up into lines, I'd want the text to wrap. However, if any single word is too large to fit, then I want the text size to shrink until that word is small enough to fit, regardless of whether or not that text is also wrapping. I don't care how much vertical space the text takes up.
Here's an example I put together manually in Excel to demonstrate the intended behavior:
In example 1 the whole text fits in the box.
In example 2 the text is two words, so it can be wrapped without shrinking the text.
In example 3, the single word is too long so the text has to be shrunk.
In example 4 the text can be wrapped but it still contains a word that is too long to fit, so the text has to be shrunk until that longest word can fit.
How can I accomplish this in WPF? I haven't been able to find a combination of ViewBox and TextBlock.TextWrapping which does this.
EDIT:
If I do have to do this manually (which would be a bit of a nightmare), then is there at least a way I can figure out what the TextBlock decides is a "line"? I would need to know how it's going to break up the text before I could identify if any one "line" is going to be too long.
You have to do this manually. The following code sample adjusts the font size of a TextBox until all text fits in to the viewport (maximum available space for text rendering). You have to execute this method from an event handler that is registered to the TextBoxBase.TextChanged event:
protected void ResizeTextToFit(TextBox textBox)
{
// Make sure the first line is always visible
textBox.ScrollToVerticalOffset(0);
bool fontSizeHasChanged = false;
// Shrink to fit as long
// the last visible line is not the last line or
// the true text height is bigger than the visible text height
// and prevent font size to be set to '0'
while (textBox.FontSize > 1
&& (textBox.GetLastVisibleLineIndex() < textBox.LineCount - 1
|| textBox.ExtentHeight > textBox.ViewportHeight))
{
fontSizeHasChanged = true;
textBox.FontSize -= 1.0;
}
if (fontSizeHasChanged)
{
return;
}
// Enlarge to fit as long the last line is visible
// and the text height fits into the viewport
while (textBox.GetLastVisibleLineIndex() == textBox.LineCount - 1
&& textBox.ExtentHeight < textBox.ViewportHeight)
{
textBox.FontSize += 1.0;
}
textBox.FontSize -= 1.0;
}
You may prefer to extend your own class from TextBox to encapsulate this behavior.
This example depends on a TextBox that has a fixed Width and Height, so that it cann't resize to the content.
Seeing as no real solution to this exists, I ended up coding it myself:
Imports System.ComponentModel
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Documents
Public Class TextScalerBehavior
Public Shared ReadOnly ShrinkToFitProperty As DependencyProperty = DependencyProperty.RegisterAttached("ShrinkToFit", GetType(Boolean), GetType(TextScalerBehavior), New PropertyMetadata(False, New PropertyChangedCallback(AddressOf ShrinkToFitChanged)))
Public Shared Function GetShrinkToFit(obj As TextBlock) As Boolean
Return obj.GetValue(ShrinkToFitProperty)
End Function
Public Shared Sub SetShrinkToFit(obj As TextBlock, value As Boolean)
obj.SetValue(ShrinkToFitProperty, value)
End Sub
Protected Shared Sub ShrinkToFitChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
Dim tb As TextBlock = d
If e.NewValue Then
tb.AddHandler(TextBlock.SizeChangedEvent, TargetSizeChangedEventHandler)
With DependencyPropertyDescriptor.FromProperty(TextBlock.TextProperty, GetType(TextBlock))
.AddValueChanged(tb, TargetTextChangedEventHandler)
End With
tb.AddHandler(TextBlock.LoadedEvent, TargetLoadedEventHandler)
Else
tb.RemoveHandler(TextBlock.SizeChangedEvent, TargetSizeChangedEventHandler)
With DependencyPropertyDescriptor.FromProperty(TextBlock.TextProperty, GetType(TextBlock))
.RemoveValueChanged(tb, TargetTextChangedEventHandler)
End With
tb.RemoveHandler(TextBlock.LoadedEvent, TargetLoadedEventHandler)
End If
End Sub
Protected Shared ReadOnly TargetSizeChangedEventHandler As New RoutedEventHandler(AddressOf TargetSizeChanged)
Protected Shared Sub TargetSizeChanged(Target As TextBlock, e As RoutedEventArgs)
Update(Target)
End Sub
Protected Shared ReadOnly TargetTextChangedEventHandler As New EventHandler(AddressOf TargetTextChanged)
Protected Shared Sub TargetTextChanged(Target As TextBlock, e As EventArgs)
Update(Target)
End Sub
Protected Shared ReadOnly TargetLoadedEventHandler As New RoutedEventHandler(AddressOf TargetLoaded)
Protected Shared Sub TargetLoaded(Target As TextBlock, e As RoutedEventArgs)
Update(Target)
End Sub
Private Shared ReadOnly Shrinkging As New HashSet(Of TextBlock)
Protected Shared Sub Update(Target As TextBlock)
If Target.IsLoaded Then
Dim Clip = Primitives.LayoutInformation.GetLayoutClip(Target)
If Clip IsNot Nothing Then
If Not Shrinkging.Contains(Target) Then Shrinkging.Add(Target)
Target.FontSize -= 1
ElseIf Target.FontSize < TextElement.GetFontSize(Target.Parent) Then
If Shrinkging.Contains(Target) Then
Shrinkging.Remove(Target)
Else
Target.FontSize += 1
End If
End If
End If
End Sub
End Class
This class implements the behavior I need as a WPF attached behavior using attached dependency properties. The magic happens in the final routine: Update.
In WPF, if a given element is being clipped (i.e. it's bigger than the space it's allowed to take up, so it gets cut off), then LayoutInformation.GetLayoutClip returns data on what area of the element is visible. If an element is not clipped, this seems to return null (though the docs don't say that).
A TextBlock with TextWrapping="WrapWithOverflow" will "overflow" past the edges of its container if any single line is too big to be broken correctly.
The Update routine checks to see if this clipping is occurring and if so lowers the font size by 1. This changes the size of the TextBlock and triggers another round of Update, which continues the cycle until the element no longer clips.
There is also additional logic to scale the font back up to its original size if the available space increases.
An example of usage is:
<TextBlock [YourNamespace]:TextScalerBehavior.ShrinkToFit="True" TextWrapping="WrapWithOverflow"/>
Remember that TextWrapping="WrapWithOverflow" is required.

Extend Width In Both Directions

I'm am trying to animate a box width to extend in both left and right direction.
Of course it's only extending in the right direction, how would I have it extend in both. Most WPF codes are in C# so I can't really find an answer from Google.
Imports System.Windows.Media.Animation
Class MainWindow
Private Sub EnlargeBtn_Click(sender As Object, e As RoutedEventArgs) Handles EnlargeBtn.Click
Dim NewWidth = 300
Dim widthAnimation As New DoubleAnimation(NewWidth, TimeSpan.FromMilliseconds(1000))
Box.BeginAnimation(WidthProperty, widthAnimation)
End Sub
End Class
You need to animate the left position as well as the width.
If you can change the left position you'll need to change the width by twice the amount you're changing the left property by to get the box to grow uniformly in both directions.
If you can't change the left or X property of a Rectangle (because it doesn't appear to have one you can access - MSDN) then the only solution might be to put it inside another container (a Grid or Border) and set it's HorizontalAlignment to Center.
Figured it out, had to set the HorizontalAlignment to Center and move the left and right margins by half the width
Imports System.Windows.Media.Animation
Class MainWindow
Private Sub EnlargeBtn_Click(sender As Object, e As RoutedEventArgs) Handles EnlargeBtn.Click
Dim NewWidth = 300
Dim widthAnimation As New DoubleAnimation(NewWidth, TimeSpan.FromSeconds(1))
Dim marginAnimation As New ThicknessAnimation(New Thickness(Box.Margin.Left - NewWidth / 2, Box.Margin.Top, Box.Margin.Right - NewWidth / 2, 0), TimeSpan.FromSeconds(1))
Box.BeginAnimation(WidthProperty, widthAnimation)
Box.BeginAnimation(MarginProperty, marginAnimation)
End Sub
End Class

WPF - programmatically referencing image resource in codebehind of Window with no XAML

Consider a class that inherits from System.Windows.Window like so:
Public Class MyWindow
Inherits Window
Private _root As Grid
Public Sub New()
MyBase.New()
_root = New Grid
Me.AddChild(_root)
End Sub
Private Sub Me_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Dim image As New Image
Dim bitmapImage As New BitmapImage(New Uri("Assets/MyImage.png", UriKind.Relative))
image.Source = bitmapImage
image.Width = 100
image.Height = 100
image.Stretch = Stretch.Fill
_root.Children.Add(image)
End Sub
End Class
Let it be part of a WPF Windows Application that has the following module as its startup object:
Module MyModule
Sub main()
Dim myApplication As New Application
myApplication.Run(New MyWindow)
End Sub
End Module
The window gets displayed, but the image does not. When inserting the exact same image loading code in the Loaded event of a VS default MainWindow class (MainWindow.xaml.vb), the image shows up as expected. MyImage.png has 'Build Action' set to 'Resource' in both cases. What am I missing here?
Edit:
I learned that such references in codebehind must be specified using the Pack URI scheme, so replacing the Uri code with
New Uri("pack://application:,,,/Assets/MyImage.png")
will make it work. The problem was that the relative Uri was interpreted as 'file system absolute' (despite having specified UriKind.Relative), and the image location got resolved to C:\Assets\MyImage.png.
But that doesn't answer the underlying question: Why does
New Uri("Assets/MyImage.png", UriKind.Relative)
work when used in the codebehind of the standard MainWindow class (which also inherits Window, but additionally has some associated XAML), but not in a 'bareboned' descendant of Window like the MyWindow class above (defined in code only)?
Apparently, the Uri was interpreted as absolute - even though I specified it as UriKind.Relative.
When replacing with
Dim bitmapImage As New BitmapImage(New Uri("pack://application:,,,/Assets/MyImage.png"))
it works.

wpf - vb - animating gridsplitter / dock panel

this relates to my earlier question - I want to animate a grid splitter (to make panels slide into / out of view). We are pretty good at VB and already have a VB project, so would like to stay with VB if we can, but most WPF examples seem to be in XAML or CS.
I have some simple VB animation code working, BUT:
Of course, what needs to be animated is the width / height of the grid column / row, and this is not a dependency property. I found some clever stuff in CS to make a dependency property but could not translate this to vb. So I found a simple workaround which is to animate a dockpanel in the grid cell, catch it's size changed events and use these to set the cell grid size. It works but I wonder if it's less efficient as 2 things are being changed separately? Also I have to (when the animation completes) set the grid cells sizes back to * in the right proportion, and the dockpanel size back to auto.
It works, but it seems a bit clumsy - does someone have an example of making the animation for the grid work directly from VB any any other suggestions?
Thanks
For reference, here is the VB code for a dependency property to animate a gridsplitter:
Public Class GridLengthAnimation
Inherits AnimationTimeline
Public Sub New()
End Sub
Public Property From() As GridLength
Get
Return DirectCast(GetValue(FromProperty), GridLength)
End Get
Set(value As GridLength)
SetValue(FromProperty, value)
End Set
End Property
Public Shared ReadOnly FromProperty As DependencyProperty
= DependencyProperty.Register("From", GetType(GridLength),
GetType(GridLengthAnimation))
Public Property [To]() As GridLength
Get
Return DirectCast(GetValue(ToProperty), GridLength)
End Get
Set(value As GridLength)
SetValue(ToProperty, value)
End Set
End Property
Public Shared ReadOnly ToProperty As DependencyProperty
= DependencyProperty.Register("To", GetType(GridLength),
GetType(GridLengthAnimation))
Public Overrides ReadOnly Property TargetPropertyType() As Type
Get
Return GetType(GridLength)
End Get
End Property
Protected Overrides Function CreateInstanceCore() As Freezable
Return New GridLengthAnimation()
End Function
Public Overrides Function GetCurrentValue
(defaultOriginValue As Object,
defaultDestinationValue As Object,
animationClock As AnimationClock) As Object
Dim fromValue As Double = Me.From.Value
Dim toValue As Double = Me.[To].Value
If fromValue > toValue Then
Return New GridLength((1 - animationClock.CurrentProgress.Value)
* (fromValue - toValue) + toValue,
If(Me.[To].IsStar, GridUnitType.Star, GridUnitType.Pixel))
Else
Return New GridLength((animationClock.CurrentProgress.Value) *
(toValue - fromValue) + fromValue,
If(Me.[To].IsStar, GridUnitType.Star, GridUnitType.Pixel))
End If
End Function
End Class

Cannot update a treeview inside a usercontrol

I created a usercontrol with a treeview inside it. The treeview will be populated if I add nodes in the onload handler of the usercontrol. But after that(for example, I click a button in its parent form), the treeview will not refresh. I can see the nodes was updated in memory, but it just cannot display on the screen. I called refresh/update after adding nodes. Any suggestion is appreciated.
I put a quick test together based on your description and it seems to paint just fine.
UserControl1
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class UserControl1
Inherits System.Windows.Forms.UserControl
'UserControl overrides dispose to clean up the component list.
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.TreeView1 = New System.Windows.Forms.TreeView
Me.SuspendLayout()
'
'TreeView1
'
Me.TreeView1.Dock = System.Windows.Forms.DockStyle.Fill
Me.TreeView1.Location = New System.Drawing.Point(0, 0)
Me.TreeView1.Name = "TreeView1"
Me.TreeView1.Size = New System.Drawing.Size(150, 150)
Me.TreeView1.TabIndex = 0
'
'UserControl1
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.Controls.Add(Me.TreeView1)
Me.Name = "UserControl1"
Me.ResumeLayout(False)
End Sub
Friend WithEvents TreeView1 As System.Windows.Forms.TreeView
End Class
Public Class UserControl1
Public Sub AddNewNode(ByVal text As System.String)
TreeView1.Nodes.Add(text)
End Sub
End Class
Put the usercontrol on a form with a button
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
UserControl11.AddNewNode(Now.ToString)
End Sub
End Class
If you are seeing proper painting as well then look at any graphics handling in the parent form then the usercontrol then the controls within the usercontrol. We really need more info.
Thank you, Dave. I figured it out. I put the usercontrol twice to my form by mistake(I cannot remember how I did it). And the one I operate is underneath the other one. That's why I cannot see it. Sorry for wasting your time.

Resources