I'm working on a rather big project, that's why I can't really paste working code, but maybe you can give me a hint on what's wrong from reading the symptoms. Otherwise I have to see how I can re-create the problem in a smaller project to paste it in here.
I have a custom control that inherits from TreeView, let's call it MyTreeView. This control is using its own containers, let's call them MyTreeViewItems.
The whole thing is styled and tweaked that it looks like a family tree or an org chart (horizontally instead of vertically, parents on top, children below). The connecting lines are drawn in the OnRender procedure of MyTreeView.
Protected Overrides Sub OnRender(drawingContext As DrawingContext)
Dim bounds As ItemBounds = Nothing
If (Me.ConnectorPen IsNot Nothing) Then
Me.GetItemBounds(Me, bounds)
Me.DrawConnections(Me.ConnectorPen, drawingContext, bounds)
End If
MyBase.OnRender(drawingContext)
End Sub
ConnectorPen is a dependency property of type Pen and ItemBounds is a small hierarchical helper class holding the bounding rectangles of the content of all MyTreeViewItems.
GetItemBounds collects the bounding rectangles of the content of every MyTreeViewItem.
Private Sub GetItemBounds(parent As ItemsControl, ByRef bounds As ItemBounds)
Dim tvi As MyTreeViewItem
Dim topLeft As Point
Dim bottomRight As Point
Dim current As ItemBounds = Nothing
For Each item As Object In parent.Items
tvi = TryCast(parent.ItemContainerGenerator.ContainerFromItem(item), MyTreeViewItem)
If (tvi IsNot Nothing) Then
topLeft = tvi.Content.TranslatePoint(New Point(0, 0), Me)
bottomRight = tvi.Content.TranslatePoint(New Point(tvi.Content.ActualWidth, tvi.Content.ActualHeight), Me)
If (bounds Is Nothing) Then
bounds = New ItemBounds
bounds.Bounds = New Rect(topLeft, bottomRight)
current = bounds
Else
current = New ItemBounds
current.Bounds = New Rect(topLeft, bottomRight)
bounds.Children.Add(current)
End If
End If
Me.GetItemBounds(tvi, current)
Next item
End Sub
tvi.Content is the ContentPresenter holding the content of the current item.
DrawConnections draws the connecting lines according to the collected item bounds:
Private Sub DrawConnections(p As Pen, drawingContext As DrawingContext, bounds As ItemBounds)
Dim minChildTop As Double
Dim middle As Double
If (bounds Is Nothing) OrElse (bounds.Children Is Nothing) OrElse (bounds.Children.Count = 0) Then
Exit Sub
End If
If (bounds.Children.Count > 1) Then
minChildTop = bounds.Children.Min(Of Double)(Function(x As ItemBounds) x.Bounds.Top)
middle = bounds.Bounds.Bottom + ((minChildTop - bounds.Bounds.Bottom) / 2)
drawingContext.DrawLine(p, bounds.Bounds.BottomCenter,
New Point(bounds.Bounds.BottomCenter.X, middle))
drawingContext.DrawLine(p, New Point(bounds.Children.First.Bounds.TopCenter.X, middle),
New Point(bounds.Children.Last.Bounds.TopCenter.X, middle))
End If
For Each item As ItemBounds In bounds.Children
If (bounds.Children.Count > 1) Then
drawingContext.DrawLine(p, New Point(item.Bounds.TopCenter.X, middle),
item.Bounds.TopCenter)
Else
drawingContext.DrawLine(p, bounds.Bounds.BottomCenter,
item.Bounds.TopCenter)
End If
Me.DrawConnections(p, drawingContext, item)
Next item
End Sub
BottomCenter and TopCenter are extension methods for Rect to determine the Point which cuts the top edge or the bottom edge into two same-length halves.
Everything works fine so far. The space between one parent item and it's children is created by a fix margin that is added to the StackPanel, that is used as the ItemsPanel in the default style of MyTreeViewItem.
<Style TargetType="{x:Type local:MyTreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
IsItemsHost="True"
Margin="0,20,0,0" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
...
</Style>
Now the customer wants this space between parent and children to be changeable from outside the control. So I added a dependency property called ItemSpacing of type Double to MyTreeView, and changed the default style of MyTreeViewItem to use this property instead of the fixed margin (conversion from Double to Thickness is done by a converter).
<Style TargetType="{x:Type local:MyTreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
IsItemsHost="True"
Margin="{Binding ItemSpacing, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MyTreeView}}, Converter=..." />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
...
</Style>
Now the child items use the space provided by ItemSpacing (so that works), but the drawing of the connecting lines fails. It looks like the collecting of the bounding rectangles of the content of all MyTreeViewItems is done before this bound margin is applied.
I already tried to play around with the AffectsRender and AffectsArrange settings of the ItemSpacing dependency property, but that didn't help.
Can you tell me, why this is working with a fix margin and why it isn't working when the margin comes from a binding?
Related
I have this grid:
<Grid x:Name="topGrid" Height="100" VerticalAlignment="Top" Margin="10,0,0,0" />
In my code if I set the background like this:
topGrid.Background = "#FF3C3C3C".ToBrush()
Using this extension:
Module Extensions
<Extension()>
Function ToBrush(ByVal HexColorString As String) As SolidColorBrush
Return CType((New BrushConverter().ConvertFrom(HexColorString)),
SolidColorBrush)
End Function
End Module
I can change background just fine, but i have around 20 grids on my form and i want to change backgrounds of all grids at once using bindings.
I have tried doing it like this:
This is xml:
<Grid x:Name="topGrid" Background="{Binding MyBackgroundColor}" Height="100" VerticalAlignment="Top" Margin="10,0,0,0" >
This is code:
Private Sub button1_Click(sender As Object, e As RoutedEventArgs) Handles button1.Click
MyBackgroundColor = "#FF3C3C3C".ToBrush()
End Sub
Private _myBackgroundColor As SolidColorBrush
Public Property MyBackgroundColor() As SolidColorBrush
Get
Return _myBackgroundColor
End Get
Set
_myBackgroundColor = Value
End Set
End Property
Public Sub New()
InitializeComponent()
End Sub
If you want to change all the backgrounds on many grids then a style is another way to go.
Although this is c# there's very little code and you could run it through an online converter.
I've done this in app.xaml for quickness but you'd want to put it in a resource dictionary merged in app.xaml in a proper app.
<Application.Resources>
<SolidColorBrush x:Key="gridBackgroundBrush" Color="Blue"/>
<Style TargetType="{x:Type Grid}">
<Setter Property="Background" Value="{DynamicResource gridBackgroundBrush}"/>
</Style>
</Application.Resources>
</Application>
And you can change that brush:
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Color colour = (Color)ColorConverter.ConvertFromString("#FFD700");
Application.Current.Resources["gridBackgroundBrush"] = new SolidColorBrush(colour);
}
If you don't want one or two grids to have this behaviour you could just set their background to white or transparent and this will have precedence over the style.
If your requirement is instead more complicated then you could lose the style and instead use the resource directly as DynamicResource. Which might be what Clemens means.
<Grid Background="{DynamicResource gridBackgroundBrush}"
I am making an explorer control in my WPF application using a Treeview and Listview. For the Listview I would like to show the contents of the selected folder using 32x32 pixel icons. To achieve this I have the following XAML which is also truncating long file/folder names within a TextBlock:
<ListView x:Name="LV_Explore"
Grid.Column="2"
BorderThickness="0"
VerticalContentAlignment="Top"
ItemsSource="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Visible"
AllowDrop="True">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="SP_ExploreItem"
Width="42">
<Image Source="{Binding LargeIcon, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,2" />
<TextBlock x:Name="TXT_ExploreItem"
Width="42"
Height="42"
TextOptions.TextFormattingMode="Display"
TextAlignment="Center"
TextWrapping="Wrap"
TextTrimming="WordEllipsis"
LineStackingStrategy="BlockLineHeight"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
When selecting a ListViewItem I would like for it to overlap the items (files/folders) below rather than the current behaviour which is to increase the height of all of the ListViewItems in the current row of the WrapPanel.
To get to the ListViewItem I am using the following code in order to show all of the text in the TextBlock when an item is selected:
Private Sub LV_Explore_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles LV_Explore.SelectionChanged
If LV_Explore.SelectedItems.Count = 1 Then
Dim LVItem As ListViewItem = TryCast(Me.LV_Explore.ItemContainerGenerator.ContainerFromIndex(Me.LV_Explore.SelectedIndex), ListViewItem)
If LVItem IsNot Nothing Then
Dim BD As Border = TryCast(VisualTreeHelper.GetChild(LVItem, 0), Border)
If BD IsNot Nothing Then
Dim CP As ContentPresenter = TryCast(BD.Child, ContentPresenter)
If CP IsNot Nothing Then
Dim SP As StackPanel = TryCast(LVItem.ContentTemplate.FindName("SP_ExploreItem", CP), StackPanel)
If SP IsNot Nothing Then
Dim TB As TextBlock = TryCast(SP.FindName("TXT_ExploreItem"), TextBlock)
TB.TextTrimming = TextTrimming.None
TB.Height = Double.NaN
End If
End If
End If
End If
End If
End Sub
For reference I want to achieve similar to the behaviour of the desktop e.g: Screenshot 1: Normal state Screenshot 2: Selected state
Whereas I currently have the normal state working fine and the selected state appears like this: Screenshot 3: Listview selected state
I suspect that I may need to change from using a WrapPanel to a Canvas in the ItemsPanelTemplate of the ListView control which will then lose the behaviour of lining up and wrapping items within the Listview automatically. So I am looking for suggestions of the best approach to use here to maintain the layout and to allow me to overlap items when selected.
Thanks,
Jay
As you may realise, something in a listview item is inside a sort of a box and it isn't getting out of that. It will be truncated or clipped if you don't let it grow.
A canvas doesn't clip so that would tick one box of your requirement, you would have to write a custom panel based on a canvas or the base panel control and write your own measure/arrange code.
This is non trivial and any scrolling could make it even more complicated, but you could take a look at people's work:
eg
https://www.codeproject.com/Articles/37348/Creating-Custom-Panels-In-WPF
Alternately, you could use an adorner.
The adorner layer is on top of everything else in your window but still in the same datacontext of whatever it's associated with.
These are not so easy to work with but again you could base your code on someone else's:
http://www.nbdtech.com/Blog/archive/2010/07/12/wpf-adorners-part-4-ndash-simple-and-powerful-system-for.aspx
An adorner would probably be my first candidate, if I follow what you want correctly.
Not many people use vb nowadays and I'm afraid you're going to find pretty much every time you look at samples they are c#.
I'm attempting to implement Excel-like column filtering and sorting. To do this, I used a DataTemplate to define the Column Header.
<DataGrid x:Name="dataGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" CanUserSortColumns="False">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="23"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="ExcelFilterButton" Tag="{Binding}" Click="ExcelFilterButton_Click" Margin="0,0,0,0" BorderThickness="0" Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" Focusable="False" Grid.Column="0">
<Image Source="Resources\NoSortNoFilter.png" Width="19" Height="19" />
</Button>
<TextBlock Text="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
</DataGrid>
And it comes out nicely.
I tried using VisualTreeHelper to find the image from the Column Header, but the Header property is a string. I've tried using the HeaderStyle and HeaderTemplate properties also but to no avail.
Using a WPF Spy program called Snoop, I can see the image in there, but still can't figure out how to access it in code. The reason I need to access it in code is to change the image based on whether that column is sorted and/or filtered. (Could this be done in XAML?)
Ok, I figured out how to do it. This most likely not the right way to do it, but I found a way that works.
To give you a little about the process.
The user clicks a header button. The buttons Tag property is bound to the column header.
The click event handler instantiates the context menu and sets its Tag to equal the button Tag.
The user clicks on a menu item.
The event handler sends the Context Menu Tag property and image name to the routine that finds the button, and then the image in the button, and changes the image.
now for the code.
The button click event handler:
Private Sub ExcelFilterButton_Click(sender As Object, e As RoutedEventArgs)
With DirectCast(Resources("sortContextMenu"), ContextMenu)
.Tag = DirectCast(sender, Button).Tag
.IsOpen = True
End With
End Sub
The menu item click event handler
Private Sub ContextMenuItem_Click(Sender As Object, e As RoutedEventArgs)
If TypeOf Sender Is MenuItem Then
'just testing, of course this isn't all this handler does.
SetColumnSortImage(Sender.Tag, "Filtered")
End If
End Sub
The SetColumnSortImage routine, which calls the two following routines.
Private Sub SetColumnSortImage(Tag As String, ImageName As String)
Dim btn As Button = Nothing
GetSortButton(Of Button)(dataGrid, Tag, btn)
If btn IsNot Nothing Then
Dim img As Image = GetChildOfType(Of Image)(btn)
img.Source = New BitmapImage(New Uri("pack://application:,,,/Resources/" & ImageName & ".png"))
End If
End Sub
The GetSortButton routine
Private Sub GetSortButton(Of T As DependencyObject)(dep As DependencyObject, Tag As String, ByRef out As DependencyObject)
If dep IsNot Nothing Then
If TypeOf dep Is Button AndAlso CType(dep, Button).Tag = Tag Then
out = dep
Else
If VisualTreeHelper.GetChildrenCount(dep) > 0 Then
For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(dep) - 1
GetSortButton(Of T)(VisualTreeHelper.GetChild(dep, i), Tag, out)
Next
End If
End If
End If
End Sub
This routine was found elsewhere on StackOverflow in C#. I converted it to VB.
Private Function GetChildOfType(Of T As DependencyObject)(depObj As DependencyObject) As T
If depObj Is Nothing Then
Return Nothing
End If
For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(depObj) - 1
Dim child = VisualTreeHelper.GetChild(depObj, i)
Dim result = If(TryCast(child, T), GetChildOfType(Of T)(child))
If result IsNot Nothing Then
Return result
End If
Next
Return Nothing
End Function
You may have a better way. Please post if you do.
I'm creating an application where we open a new window that has options to choose from in a listview, we extract one of the items in the listview and pass it on to another listview on a different window. I can populate the listview on the main window and can verify that the items are getting added during debug with an .items.count but they aren't getting added realtime. If I do a .show to open another instance of the window I see the items there but since I'm not planning on closing the main window out, how do you 'refresh' the listview to show the options that are clearly there?
I've read that if the listview is binded to a table, you can just re-bind the data to refresh it but since this listview isn't binded to anything and is just adding items to it with the .add function, I'm not sure how to 'refresh' it to show the values.
Edit:
Here is my code currently,
Public Class ObservableColl
Public Structure Steps
Private _Steps As List(Of String)
Property Steps() As List(Of String)
Get
Return _Steps
End Get
Set(ByVal value As List(Of String))
_Steps = value
End Set
End Property
End Structure
Dim songs As New ObservableCollection(Of Steps)
End Class
Private Sub CloseForm()
Dim stepArr As String() = Split(sCommon.presetSteps, ",")
Dim entries As New List(Of String)
For i = 0 To stepArr.Count - 1
entries.Add(stepArr(i))
Next
sCommon.form1.entries.Add(New Steps With {.Steps = entries}) 'Add list entries to the observableCollection
sCommon.form1.lvwCurrentSteps.DataContext = entries 'Assign the DataContext property to the observableCollection
If Not PresetList Is Nothing Then
Me.Close() 'Close this form and focus returns to the main window
End If
End Sub
<ListView Margin="49,23,0,33" HorizontalAlignment="Left" Width="230" Name="lvwCurrentSteps" SelectionMode="Multiple" ItemsSource="{Binding}" >
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="BorderBrush" Value="LightGray" />
<Setter Property="BorderThickness" Value="0,0,0,1" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView x:Name="myGridView">
<GridViewColumn Width="220" Header="Steps to run" DisplayMemberBinding="{Binding Path=Steps}">
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
I can validate that there are items in the listview with this method but the listview doesn't 'refresh' with these values when focus returns to the original window.
?sCommon.form1.lvwCurrentSteps.Items.Count
3
It's hard to tell from just this snippet but it looks like your logic is backwards in these two lines:
sCommon.form1.entries.Add(New Steps With {.Steps = entries}) 'Add list entries to the observableCollection
sCommon.form1.lvwCurrentSteps.DataContext = entries 'Assign the DataContext property to the observableCollection
I would expect that the DataContext you would want to use would be the collection you just added to, not the local variable that was what you added (which is a List Of String and so has no Steps property to support your DisplayMemberBinding). It also seems a little strange the way that the ObservableColl class is set up, with an ObservableCollection of objects containing a List.
I'm not sure what's happening with the count but you should validate that your basic logic is really doing what you mean it to.
I have a canvas of sorts derived from a 'Panel' used for custom drawing of lines and other geometries, all from VB code. I got this approach from a book and I'm not sure it's the best approach. The drawing part works so far for me.
But what I need is to put a textbox control on the control containing text which can be edited by the user. The textbox needs to be placed at coordinates determined dynamically and later deleted. There will probably be other controls handled so.
The following code does nothing:
tb = New TextBox()
tb.Text = "How now brown cow?"
tb.BorderThickness = New Thickness(3)
tb.BorderBrush = Brushes.CadetBlue
drawingSurface.Children.Add(tb)
This is the definition of my DrawingCanvas:
Public Class DrawingCanvas
Inherits Panel
Private visuals As New List(Of Visual)()
Private hits As New List(Of DrawingVisual)()
Protected Overrides Function GetVisualChild(ByVal index As Integer) As Visual
Return visuals(index)
End Function
Protected Overrides ReadOnly Property VisualChildrenCount() As Integer
Get
Return visuals.Count
End Get
End Property
Public Sub AddVisual(ByVal visual As Visual)
visuals.Add(visual)
MyBase.AddVisualChild(visual)
MyBase.AddLogicalChild(visual)
End Sub
Public Sub DeleteVisual(ByVal visual As Visual)
visuals.Remove(visual)
MyBase.RemoveVisualChild(visual)
MyBase.RemoveLogicalChild(visual)
End Sub
Public Function GetVisual(ByVal point As Point) As DrawingVisual
Dim hitResult As HitTestResult = VisualTreeHelper.HitTest(Me, point)
Return TryCast(hitResult.VisualHit, DrawingVisual)
End Function
Public Function GetVisuals(ByVal region As Geometry) As List(Of DrawingVisual)
hits.Clear()
Dim parameters As New GeometryHitTestParameters(region)
Dim callback As New HitTestResultCallback(AddressOf Me.HitTestCallback)
VisualTreeHelper.HitTest(Me, Nothing, callback, parameters)
Return hits
End Function
Private Function HitTestCallback(ByVal result As HitTestResult) As HitTestResultBehavior
Dim geometryResult As GeometryHitTestResult = CType(result, GeometryHitTestResult)
Dim visual As DrawingVisual = TryCast(result.VisualHit, DrawingVisual)
If visual IsNot Nothing AndAlso geometryResult.IntersectionDetail = IntersectionDetail.FullyInside Then
hits.Add(visual)
MsgBox("Ouch")
End If
Return HitTestResultBehavior.Continue
End Function
End Class
Here is the XAML. I added a textbox to the DrawingCanvas just to see if something appears. Nothing did. In fact, I want to do this in code, not XAML. I thought I could hide or move it around dynamically.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Music"
Title="MainWindow" Height="539" Width="892">
<DockPanel>
<Menu DockPanel.Dock="Top" Name="MainMenu" VerticalAlignment="Top" Height="25">
<MenuItem Name="File" Header="File">
<MenuItem Name="Open" Header="Bla bla..."/>
</MenuItem>
</Menu>
<local:DrawingCanvas DockPanel.Dock="Bottom" x:Name="drawingSurface" RenderTransformOrigin="0.5,0.5" >
<TextBox Height="0" Name="TextBox1" Width="45" Text="How now brown cow?" />
</local:DrawingCanvas>
</DockPanel>
</Window>
Thanks for helping a nooby. A solution would be very useful for me. This was easy with windows forms, but I need the drawing speed of WPF.
I think you are a bit off here. In WPF you have a control called Canvas. I would suggest you use that instead of your own "DrawingCanvas", which I can't get to work btw. :( (For some reason I cant create code blocks so if someone can edit it I would be pleased)
Anyhow,
<local:DrawingCanvas DockPanel.Dock="Bottom" x:Name="drawingSurface" RenderTransformOrigin="0.5,0.5" >
<TextBox Height="0" Name="TextBox1" Width="45" Text="How now brown cow?" />
</local:DrawingCanvas>
Turns into:
<Canvas x:Name="drawingSurface">
</Canvas>
And then to add a textbox just do as your current code:
Dim tb as New TextBox
drawingSurface.Children.Add(tb)
This should give you what you need.
Heres the code for adding a rectangle to your canvas.
Private Sub DrawBackground()
Dim Rect As New Rectangle()
Rect.Height = 50
Rect.Width = 50
Rect.Fill = Brushes.Cornsilk
drawingSurface.SetTop(Rect, 30)
drawingSurface.SetLeft(Rect, 100)
drawingSurface.Children.Add(Rect)
End Sub
I'll add another answer that might be more in the line of what you are looking for. This is a class that inherits from Canvas that will allow you to draw stuff in the same way as say you do in your comment.
I also creates a textbox on a random location when it is created.
Public Class DrawingCanvas
Inherits Canvas
Public RandomTextBox As New TextBox
Protected Overrides Sub OnRender(dc As System.Windows.Media.DrawingContext)
Dim brush As Brush = Brushes.Black
Dim drawingPen As Pen = New Pen(Brushes.Green, 3)
dc.DrawRectangle(brush, drawingPen, New Rect(5, 5, Me.ActualWidth - 5, Me.ActualHeight - 5))
RandomTextBox.Text = "Herpdiderp"
If Not Me.Children.Contains(RandomTextBox) Then
Dim r As New Random()
RandomTextBox.Height = 23
RandomTextBox.Width = 100
Me.SetTop(RandomTextBox, r.Next(0, Me.ActualHeight - RandomTextBox.Height))
Me.SetLeft(RandomTextBox, r.Next(0, Me.ActualWidth - RandomTextBox.Width))
Me.Children.Add(RandomTextBox)
End If
End Sub
End Class
This is not a full answer. #WozzeC, you were right about using the canvas - almost.
I have managed to solve this in xaml alone - I want to eventually solve it in vb.net.
<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">
<Grid>
<DockPanel HorizontalAlignment="Stretch" Name="DockPanel1" VerticalAlignment="Stretch" >
<Menu Height="23" DockPanel.Dock="Top" Name="Menu1" VerticalAlignment="Top" />
<Canvas Name="Canvas1" Background="Aquamarine">
<TextBox Canvas.Left="118" HorizontalScrollBarVisibility="Disabled" Canvas.Top="81" AcceptsReturn="True" Height="auto" Name="TextBox1" Width="68" Text="Herpdiderp" BorderThickness="0" Background="Aquamarine" />
</Canvas>
</DockPanel>
</Grid>
And here is a piece of code that expands the text as needed. I think it's almost totally cool. It expands both to the right and downwards, as if you're actually typing on the form. It adds a little too much on the right, but it's not visible in this version because the background color is the same.
Here is the event code that expands it to the right.
Imports System.Globalization
Class MainWindow
Private Sub TextBox1_TextChanged(sender As System.Object, e As System.Windows.Controls.TextChangedEventArgs) Handles TextBox1.TextChanged
Dim ft As New FormattedText(TextBox1.Text, CultureInfo.GetCultureInfo("en-us"), FlowDirection.LeftToRight, New Typeface("Verdana"), 16, Brushes.Black)
TextBox1.Width = ft.Width
End Sub
End Class
I tried this with my existing solution and the textbox does not appear. I made the DrawingCanvas into a plain Canvas and commented out all the code referring to the DrawingCanvas. And the textbox does appear. The problem is this: I need the functionality in the DrawingCanvas - which derives from Canvas. But because the baseclass methods are Protected, I can't get to them. I can only use them in a derived class, unless there is another way I don't know about.
Any ideas about how to solve this?