Preserving indenting when wrapping in a wpf textblock control - wpf

I have a WPF textblock set up with the property TextWrapping="Wrap".
When I pass in a long string with a tab character (vbTab in my case) at the start, I would like the wrapping to honour this and keep the wrapped parts of the string indented.
For example, instead of:
[vbTab]thisisreallylong
andwrapped
I want
[vbTab]thisisreallylong
[vbTab]andwrapped
and ideally for multiple tabs, etc. too.
[edit - additional details]
Because the textblock will be of variable size and contain multiple lines of text with various amounts of indenting, I can't just have a margin or manually split the strings and add tabs.
Essentially what I want is for it to treat lines of text like paragraphs, that keep their indenting when they wrap.

Based on your idea, I am able to come up with this solution
I'll convert all the tabs in the beginning of every line to .5 inch margin each and will add the same text in a paragraph and apply the calculated margin to the same
A TextBlock was not feasible for the same as it is useful for basic text inlines like run bold, inline ui container etc. adding paragraph was more complicated in a TextBlock so I made the solution based on FlowDocument.
Result
below example demonstrate the same using FlowDocumentScrollViewer or RichTextBox or FlowDocumentReader or plain FlowDocument
I have created the solution using attached properties, so you can attach the same to any of the mentioned or even add your own host for the document. you simply have to set IndentationProvider.Text to the desired host.
XAML
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:l="clr-namespace:PreservingIndentationDemo"
Title="MainWindow"
Height="350"
Width="525">
<Window.Resources>
<sys:String x:Key="longString"
xml:space="preserve"> this is really long and wrapped
another line this is also really long and wrapped
one more line this is also really long and wrapped
another line this is also really long and wrapped
another line this is also really long and wrapped
</sys:String>
</Window.Resources>
<Grid>
<FlowDocumentScrollViewer l:IndentationProvider.Text="{StaticResource longString}" />
<!--<RichTextBox l:TextToParaHelper.Text="{StaticResource longString}" IsReadOnly="True"/>-->
<!--<FlowDocumentReader l:TextToParaHelper.Text="{StaticResource longString}" />-->
<!--<FlowDocument l:TextToParaHelper.Text="{StaticResource longString}" />-->
</Grid>
</Window>
refers to tab char
IndentationProvider
Class IndentationProvider
Public Shared Function GetText(obj As DependencyObject) As String
Return DirectCast(obj.GetValue(TextProperty), String)
End Function
Public Shared Sub SetText(obj As DependencyObject, value As String)
obj.SetValue(TextProperty, value)
End Sub
' Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.RegisterAttached("Text", GetType(String), GetType(IndentationProvider), New PropertyMetadata(Nothing, AddressOf OnTextChanged))
Private Shared Sub OnTextChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
Dim blocks As BlockCollection = Nothing
Dim rtb As RichTextBox = TryCast(d, RichTextBox)
If rtb IsNot Nothing Then
rtb.Document.Blocks.Clear()
blocks = rtb.Document.Blocks
End If
If blocks Is Nothing Then
Dim fd As FlowDocument = TryCast(d, FlowDocument)
If fd IsNot Nothing Then
fd.Blocks.Clear()
blocks = fd.Blocks
End If
End If
If blocks Is Nothing Then
Dim fdr As FlowDocumentReader = TryCast(d, FlowDocumentReader)
If fdr IsNot Nothing Then
fdr.Document = New FlowDocument()
blocks = fdr.Document.Blocks
End If
End If
If blocks Is Nothing Then
Dim fdr As FlowDocumentScrollViewer = TryCast(d, FlowDocumentScrollViewer)
If fdr IsNot Nothing Then
fdr.Document = New FlowDocument()
blocks = fdr.Document.Blocks
End If
End If
Dim newValue As String = TryCast(e.NewValue, String)
If Not String.IsNullOrWhiteSpace(newValue) Then
For Each line As String In newValue.Split(ControlChars.Lf)
Dim leftMargin As Double = 0
Dim newLine As String = line
While newLine.Length > 0 AndAlso newLine(0) = ControlChars.Tab
leftMargin += 0.5
newLine = newLine.Remove(0, 1)
End While
Dim marginInch As String = leftMargin & "in"
Dim marginDip As Double = CDbl(New LengthConverter().ConvertFromString(marginInch))
Dim para As New Paragraph(New Run(newLine)) With {.Margin = New Thickness(marginDip, 0, 0, 0)}
blocks.Add(para)
Next
End If
End Sub
End Class
Demo
try demo project

Related

WPF Update lines

I'm trying to replicate in wpf(vb.net) an app that I made some times ago in winform.
Il the old app I basically open a binary file and load it in one array and then draw a 2d lines chart with it. Files are like 2/4MB size, so i put a slider and every time I change it I call the drawing sub with the slider offset.
Now I'm totaly new in wpf, I figure out how to draw the chart, but I don't understand how update it when I move the slider, or(next step) when I change array's values by code.
To make simple tests I load a file when start and when button1 clicked I modify array values and I would see the update chart, but I have not found a way.
<Window x:Class="MainWindow"
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"
xmlns:local="clr-namespace:testwpf2d"
mc:Ignorable="d"
Title="MainWindow" Height="1920" Width="1080">
<Grid x:Name="BaseGrid">
<Canvas Name="paintSurface" >
<Canvas.Background>
<SolidColorBrush Color="Black" Opacity="1"/>
</Canvas.Background>
<Button Content="Button" Canvas.Left="184" Canvas.Top="36" Width="75" Click="Button_Click"/>
</Canvas>
</Grid>
</Window>
Private Sub BaseGrid_Loaded(sender As Object, e As RoutedEventArgs) Handles BaseGrid.Loaded
Dim openFileDialogORI As OpenFileDialog = New OpenFileDialog()
If openFileDialogORI.ShowDialog = True Then
Try
Dim MyFileORIStream As FileStream = New FileStream(openFileDialogORI.FileName, FileMode.Open, FileAccess.Read)
Dim ORIBuffer As BinaryReader = New BinaryReader(MyFileORIStream)
Dim fInfo As New FileInfo(openFileDialogORI.FileName)
Dim numBytes As Long = fInfo.Length
MyORIArray = ORIBuffer.ReadBytes(CInt(numBytes))
ORIBuffer.Close()
MyFileORIStream.Close()
Dim NomeFileOri As String = openFileDialogORI.FileName
Dim info As New FileInfo(openFileDialogORI.FileName)
Dim length As Integer = CInt(info.Length)
Catch ex As Exception
MsgBox(ex.Message)
End Try
End If
MessageBox.Show("File loaded")
For i As Integer = 1 To 2000
Dim Line As New Line
Line.X1 = i
Line.X2 = i + 1
Line.Y1 = MyORIArray(i)
Line.Y2 = MyORIArray(i + 1)
Line.Stroke = Brushes.YellowGreen
Line.StrokeThickness = 0.25
BaseGrid.Children.Add(Line)
Next
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
Dim Rand As New Random
For i As Integer = 1 To 1000
MyORIArray(i) = Rand.NextDouble * 50
Next
MessageBox.Show("new data ")
BaseGrid.UpdateLayout()
paintSurface.UpdateLayout()
End Sub
I solved creating a collection of point from the array and draw a polyline, when arraypoint change(always by user interaction that raise an event(button,mouse etc), I update or recreate point collection and then use update.layout. I understand that is not the best way but for this little application work good

Eliminating adding CRLF to end of text while copying from DataGrid

If you copy text from WPF DataGrid cell (Ctrl+C), from unknown reason there is always end of line (CRLF) added to end of copied text. This can prevent correct pasting of copied text to some applications.
I tried to intercept copying using behavior and also by directly hooking to DataGrid's CopyingRowClipboardContent, both without success.
Protected Sub OnCopyingRowClipboardContent(sender As Object, e As DataGridRowClipboardEventArgs)
Dim cellContent = e.ClipboardRowContent(DirectCast(sender, DataGrid).CurrentCell.Column.DisplayIndex)
e.ClipboardRowContent.Clear()
e.ClipboardRowContent.Add(cellContent)
If Clipboard.ContainsText(TextDataFormat.UnicodeText) OrElse
Clipboard.ContainsText(TextDataFormat.Text) Then
Dim clipboardText = Clipboard.GetText()
Dim length = clipboardText.Length
If length >= 2 Then
If clipboardText(length - 1) = vbLf AndAlso clipboardText(length - 2) = vbCr Then
clipboardText = clipboardText.Substring(0, length - 2)
Clipboard.SetText(clipboardText)
End If
End If
End If
End Sub
Is there any way to copy text without added CRLF?
The easiest way to fix this is probably to create a custom DataGrid class that overrides the OnExecutedCopy method:
public class CustomDataGrid : DataGrid
{
protected override void OnExecutedCopy(ExecutedRoutedEventArgs args)
{
base.OnExecutedCopy(args);
string text = Clipboard.GetText();
if(!string.IsNullOrEmpty(text))
Clipboard.SetText(text.Replace("\r\n", string.Empty));
}
}

How to remove a record from a Listbox control in my WPF application with VB.Net code behind

I know similar questions to mine have been posted before, but I have tried various suggestions and nothing seem to work.
Here is my issue: I get the following error when trying to remove an item\from my listbox control:
Additional information: Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.
I need help removing an item from a listbox in WPF with VB.Net code-behind.
When I click a row in the listbox, I want to remove that item from the listbox.
I create alist of object 'ToLoadImages' to load into my listbox.
How would I remove the selected item from the list box and from my list ob object 'ToLoadImages'?
See my code below:
'CODE TO LOAD Listbox
Private Sub GetListToLoad(ClaimNo As String)
Dim ta As New ImagesDataSetTableAdapters.usp_SELECT_ImageTableAdapter
Dim dt As ImagesDataSet.usp_SELECT_ImageDataTable = ta.GetData(1, _ClaimNo, True)
Dim dr As DataRow
ListToLoad = New List(Of ToLoadImages)
Dim i As Integer = 0
For Each dr In dt
Dim ImgSource2() As Byte = DirectCast(dr(7), Byte())
Dim stream2 As MemoryStream = New MemoryStream
stream2.Write(ImgSource2, 0, ImgSource2.Length - 1)
stream2.Seek(0, SeekOrigin.Begin)
bitMap2 = New BitmapImage
bitMap2.BeginInit()
bitMap2.StreamSource = stream2
bitMap2.EndInit()
ListToLoad.Add(New ToLoadImages(dr(0), bitMap2))
Next
ImageListBox.ItemsSource = ListToLoad
End Sub
'Code to retrieve selected item from the listbox
'Here is where I want to add the code that will remove the selected item from my listbox and from the List of 'ToLoadImages'.
Private Sub ImageListBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles ImageListBox.SelectionChanged
Try
Dim itemsToLoad As ToLoadImages
itemsToLoad = ImageListBox.SelectedItem
Dim imageID as String = itemsToLoad.ImgID.ToString
Catch ex As Exception
MsgBox("Error encountered.")
End Try
End Sub
Class ToLoadImages
Public Class ToLoadImages
Private m_imgID As Integer
Private m_imageX As BitmapImage
Public Sub New(imgID As Integer, imagex As BitmapImage)
Me.m_imgID = imgID
Me.m_imageX = imagex
End Sub
Public Property ImgID() As Integer
Get
Return m_imgID
End Get
Set(ByVal value As Integer)
m_imgID = value
End Set
End Property
Public Property ImageX() As BitmapImage
Get
Return m_imageX
End Get
Set(ByVal value As BitmapImage)
m_imageX = value
End Set
End Property
End Class
Use ObservableCollection instead of List.
ObservableCollection
updates the changes automatically

UserControl/ElementHost Transparent Background

I need a method that sets the background of the ElementHost completely transparent or so that it does not even render in the first place.
Current Structure
In the background I have a PictureBox.
Over that there is my UserControl (Which you can Download below).
Both the PictureBox and the UserControl have a Width of 150.
As you can see in the Picture above, the UserControl is 100% Invisible.
In the UserControl is an ElementHost with a Width of 120, within that there is a WPF-Content with a Width of 100.
Everything is Transparent, except the ElementHost1.
My Code
UserControl:
Protected Overrides ReadOnly Property CreateParams() As System.Windows.Forms.CreateParams
Get
Dim cp As CreateParams = MyBase.CreateParams
cp.ExStyle = &H20
Return cp
End Get
End Property
Protected Overrides Sub OnPaintBackground(ByVal e As System.Windows.Forms.PaintEventArgs)
End Sub
Public Overrides Sub Refresh()
Parent.Invalidate(New Rectangle(Me.Location, Me.Size), True)
End Sub
Public Sub New()
InitializeComponent()
Me.SetStyle(ControlStyles.SupportsTransparentBackColor, True)
Me.BackColor = System.Drawing.Color.Transparent
ElementHost1.BackColor = System.Drawing.Color.Transparent
ElementHost1.BackColorTransparent = True
End Sub
I´ve also tried to create a Custom ElementHost:
Public Class TransElementHost
Inherits ElementHost
Public Sub TransElementHost()
Me.SetStyle(ControlStyles.SupportsTransparentBackColor, True)
Me.BackColorTransparent = True
'Me.BackColor = System.Drawing.Color.FromArgb(0, 0, 0, 0)
End Sub
Protected Overrides ReadOnly Property CreateParams() As System.Windows.Forms.CreateParams
Get
Dim cp As CreateParams = MyBase.CreateParams
cp.ExStyle = &H20
Return cp
End Get
End Property
Protected Overrides Sub OnPaintBackground(ByVal e As System.Windows.Forms.PaintEventArgs)
End Sub
Public Overrides Sub Refresh()
Parent.Invalidate(New Rectangle(Me.Location, Me.Size), True)
End Sub
End Class
My SVGTest-UserControl
Does anybody have an idea?
I know it's been really long since this was active, but if you still need it I've (finally) got a code that will draw controls' backgrounds fully transparent.
Just put this code in the control's code. It overrides the OnPaintBackground event:
Protected Overrides Sub OnPaintBackground(e As System.Windows.Forms.PaintEventArgs)
MyBase.OnPaintBackground(e)
If Parent IsNot Nothing Then
Dim index As Integer = Parent.Controls.GetChildIndex(Me)
For i As Integer = Parent.Controls.Count - 1 To index + 1 Step -1
Dim c As Control = Parent.Controls(i)
If c.Bounds.IntersectsWith(Bounds) AndAlso c.Visible = True Then
Dim bmp As New Bitmap(c.Width, c.Height, e.Graphics)
c.DrawToBitmap(bmp, c.ClientRectangle)
e.Graphics.TranslateTransform(c.Left - Left, c.Top - Top)
e.Graphics.DrawImageUnscaled(bmp, Point.Empty)
e.Graphics.TranslateTransform(Left - c.Left, Top - c.Top)
bmp.Dispose()
End If
Next
End If
End Sub
It's probably not the best fix, but you could try using the ButtonRenderer class. Put this code either in OnPaintBackground or OnPaint.
If Me.BackColor = Color.Transparent Then
Windows.Forms.ButtonRenderer.DrawParentBackground(e.Graphics, Me.ClientRectangle, Me)
End If
The ButtonRenderer class is used to draw the regular buttons; their border, background, text, image etc.
I used the above code myself to create a custom control with transparent background. (Though I see now that my control inherits from ButtonBase... But the above code is still worth a try).

Grid.GetRow() always returns 0

I am having trouble getting the row of a WPF grid that a textbox is in.
I have a grid that starts off with one RowDefinition. That row contains an "add" button that adds another rowdefinition to the grid below that row. This new row also contains an "add" button that performs the same function.
The problem I am having is that the function GetRow() always returns 0.
If I declare a button in the XAML that calls the same function, GetRow() returns the correct value. The problem seems to stem from the face that the buttons are created in codebehind.
This is the function that handles the click event of the "add" buttons:
Private Sub btnAddRow_Click(ByVal sender As System.Object, _
ByVal e As System.Windows.RoutedEventArgs)
Dim btnSender As Button = sender
Dim row As Integer
row = Grid.GetRow(btnSender)
AddRow(row)
End Sub
The function "AddRow" adds a new RowDefinition to the grid, the "add" button for that row, and a few other controls (label, textbox, etc).
Private Sub AddRow(ByVal position As Integer)
Dim rd As New RowDefinition()
rd.Height = New GridLength(35, GridUnitType.Pixel)
Me.Height += 35
myGrid.RowDefinitions.Insert(position, rd)
Dim add As New Button
add.Content = "Add Row"
add.HorizontalAlignment = Windows.HorizontalAlignment.Center
add.VerticalAlignment = Windows.VerticalAlignment.Center
AddHandler add.Click, AddressOf btnAddRow_Click
Grid.SetColumn(add, 2)
Grid.SetRow(add, position)
myGrid.Children.Add(add)
End Sub
I found this thread, but using "e.Source" or "e.OriginalSource" did not solve the problem.
Grid.GetRow and Grid.GetColumn keep returning 0
EDIT:
Here is my code. I pulled it out of the project it was in and created a new project for testing.
Class MainWindow
Private Sub btnAddRow_Click(ByVal sender As System.Object, _
ByVal e As System.Windows.RoutedEventArgs)
Dim btnSender As Button = sender
Dim row As Integer
row = Grid.GetRow(btnSender)
row = row + 1
AddRow(row)
End Sub
Private Sub AddRow(ByVal position As Integer)
If (myGrid.RowDefinitions.Count < position) Then
position = myGrid.RowDefinitions.Count
End If
For Each element In (From i As UIElement In myWaypointGrid.Children Where Grid.GetRow(i) >= position Select i).ToList()
Grid.SetRow(element, Grid.GetRow(element) + 1)
Next
Dim rd As New RowDefinition()
rd.Height = New GridLength(35, GridUnitType.Pixel)
Me.Height += 35
myGrid.RowDefinitions.Insert(position, rd)
Dim add As New Button
add.Content = "Add Row " & position
add.HorizontalAlignment = Windows.HorizontalAlignment.Center
add.VerticalAlignment = Windows.VerticalAlignment.Center
AddHandler add.Click, AddressOf btnAddRow_Click
Grid.SetColumn(add, 2)
Grid.SetRow(add, position)
myGrid.Children.Add(add)
End Sub
Private Sub MainWindow_Loaded(ByVal sender As Object, _
ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
AddRow(0)
End Sub
End Class
<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 Name="myGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75" />
</Grid.ColumnDefinitions>
</Grid>
Thanks for your help.
Are you ever calling the AddRow function prior to the first "Add" button click? Without more code, it's hard to say why this is not working.
Update to reflect the true issue:
You don't do an increment on the position variable which gets passed into this function so all your buttons are being added to row 0. That is why they all return 0 when you call GetRow

Resources