WPF TextBox wrap a long line after a number of characters - wpf

I'm looking for a way to solve the particular task: have a resizable window with textbox which has minimum width. Here is a little concept:
<Window ...
SizeToContent="Width"
ResizeMode="CanResize">
<DockPanel>
<Menu DockPanel.Dock="Top"><...></Menu>
<StatusBar DockPanel.Dock="Bottom"><...></StatusBar>
<StackPanel DockPanel.Dock="Right">
<RadioButton .../>
<...>
</RadioButton>
<TextBox MinWidth="{Binding MinWidth}"
TextWrapping="Wrap"
AcceptsReturn="True"
FontFamily="Consolas"
FontSize="{Binding FontSize}"
VerticalScrollBarVisibility="Visible"
.../>
</DockPanel>
</Window>
I have a two-column dialog with a stack of radiobuttons on the right side and textbox that takes all available space. I want to show at least N characters per line, so I'm using FormattedText to calculate the minimum width for text box to show these numbers of characters and setting the minimum width via binding. So when initialized, text box resized automatically depending on font size. So far so good.
The problem I face is that textbox is automatically resized upon typing and when string exceeds the initial width of textbox. Input line can be hundreds or thousands characters long and doesn't contain spaces or other charactes the wrap opportunity algorithm can use to wrap the line.
What I want is to mimic the behavior of Windows Notepad when WordWrap option is enabled. Notepad has some width and if I type a very long line, it automatically wraps the line without resizing the window. Wpf does resize textbox and window as well -- this is what I don't want. Also, I can't manipulate input string by inserting hidden characters so WPF can break on them as suggested in other similar questions.

If I understand your requirements correctly, you could handle the Loaded and SizeChanged events for the window and set the Width property of the TextBox accordingly:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Loaded += OnLoaded;
SizeChanged += OnSizeChanged;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
tb.Width = double.NaN;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
tb.Width = tb.ActualWidth;
}
}

Related

How to implement popup in Windows Phone

I'm implementing a templated control, which should work as virtual keyboard button - when you hold it, it displays a popup with additional options to choose.
I've implemented the popup more less in the following way:
<Grid>
<Border>Content</Border>
<Grid x:Name="gPopup" Visibility="Collapsed">
<StackPanel x:Name="spSubItems" Orientation="Horizontal" />
</Grid>
</Grid>
I show the popup by changing visibility to visible and setting negative margins for top and bottom. However, when I do that, and when the popup is actually larger than the control, the control is being resized to match its size - despite fact, that it is not inside:
How can I implement the popup, such that it won't expand the container it's on? And such that the container will still match size of its contents?
Edit: In response to comments and answers
I'm not sure if I'm understood correctly. Here's an image with explanation:
I'd like to keep the original container's size the same after showing the popup. I'm unsure how WrapPanel or DockPanel could help me with that.
The solution is simply to use Popup instead of positioned Grid.
Sample- Create a grid
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<!-- Setting a Rectangle having transparent background which set the
Visibility of popup -->
<Rectangle Name="popupRect" Fill="#80000000" Visibility="Collapsed"/>
<!—Here in the above Code we are just filling the rectangle With the transparent BackGround -->
<!—Creating A Border -->
<Border Name="popupBorder" Background="{StaticResource PhoneChromeBrush}" BorderBrush="Red" BorderThickness="2"
HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Collapsed">
<!-- Creating A grid Inside the Border and Rectangle -->
</Grid>
Create event for which popup should appear(for both cancel and appear)-
private void cancelButton_Click(object sender, RoutedEventArgs e)
{
popupRect.Visibility = Visibility.Collapsed;
popupBorder.Visibility = Visibility.Collapsed;
}
private void popupButton_Click(object sender, RoutedEventArgs e)
{
popupRect.Visibility = Visibility.Visible;
popupBorder.Visibility = Visibility.Visible;
}
It will work, I guess.
Like spook says, put your gPopup Grid in a Popup element and show it by opening the popup. This won't affect the main visual tree.
The reason the embedded grid embiggens the border is that the outer grid has to expand to hold pGrid and the border expands to fill the outer grid.

How to make TextChanged happen for copy/paste for TextBox in Silverlight?

For silverlight control TextBox, when copy/paste content for TextBox Text property, TextChanged Event not happening.
How to make paste same as typing for Text in TextBox?
Sorry, I can't reproduce this.
I used the following XAML
<StackPanel>
<TextBox TextChanged="TextBox_TextChanged" />
<TextBlock x:Name="tbk" />
</StackPanel>
and the following event handler
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
tbk.Text += "X";
}
Every time I typed in the TextBox or pasted text into it, the TextBlock gained another X.

WPF Canvas Desired size

I have the following control:
<UserControl x:Class="FooBar.AnnotationControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="400" Width="500" >
<ScrollViewer Height="400" Width="500">
<Canvas Height="400" Width="500" Name="ctlCanvas" MouseLeftButtonDown="MouseLeftButtonDownHandler" MouseWheel="Canvas_MouseWheel" >
<Canvas.RenderTransform>
<ScaleTransform x:Name="ZoomTransform" />
</Canvas.RenderTransform>
</Canvas>
</ScrollViewer>
</UserControl>
namespace FooBar
{
public partial class AnnotationControl : UserControl
{
public AnnotationControl()
{
InitializeComponent();
}
private void MouseLeftButtonDownHandler( object sender, MouseButtonEventArgs args)
{
//Do Something
}
private void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
ctlCanvas.Measure(new Size(ctlCanvas.ActualWidth * ZoomTransform.ScaleX, ctlCanvas.ActualHeight * ZoomTransform.ScaleY));
}
}
}
I'm trying to get the scroll viewer to respond to the scaling of the Canvas. The call to Canvas.Measure doesn't appear to change the Desired size of the Canvas. Any idea what is going on here?
You should NOT call Measure on your own. This method is supposed to be called in the layout step, and not somewhere else. Also a RenderTransform doesn't change your Size. The RenderTransform is applied AFTER the actual Layout is done. So you have a scrollviewer that don't need to scroll its content, because its the same size. What you might want is LayoutTransform.
Canvas is the most primitive element and it simply not designed to work with the ScrollViewer. Use Grid/StackPanel/WarPanel/UniformGrid instead.
Ok, I seem to have found a solution. It looks like I can wrap my canvas with another canvas and when I scale it, I simply set the height and width for the outer canvas = initial height and width times the current X and Y scales of the ScaleTransform.

How do I configure a TextBox control to automatically resize itself vertically when text no longer fits on one line?

How do I configure a TextBox control to automatically resize itself vertically when text no longer fits on one line?
For example, in the following XAML:
<DockPanel LastChildFill="True" Margin="0,0,0,0">
<Border Name="dataGridHeader"
DataContext="{Binding Descriptor.Filter}"
DockPanel.Dock="Top"
BorderThickness="1"
Style="{StaticResource ChamelionBorder}">
<Border
Padding="5"
BorderThickness="1,1,0,0"
BorderBrush="{DynamicResource {ComponentResourceKey TypeInTargetAssembly=dc:NavigationPane,
ResourceId={x:Static dc:NavigationPaneColors.NavPaneTitleBorder}}}">
<StackPanel Orientation="Horizontal">
<TextBlock
Name="DataGridTitle"
FontSize="14"
FontWeight="Bold"
Foreground="{DynamicResource {ComponentResourceKey
TypeInTargetAssembly=dc:NavigationPane,
ResourceId={x:Static dc:NavigationPaneColors.NavPaneTitleForeground}}}"/>
<StackPanel Margin="5,0" Orientation="Horizontal"
Visibility="{Binding IsFilterEnabled, FallbackValue=Collapsed, Mode=OneWay, Converter={StaticResource BooleanToVisibility}}"
IsEnabled="{Binding IsFilterEnabled, FallbackValue=false}" >
<TextBlock />
<TextBox
Name="VerticallyExpandMe"
Padding="0, 0, 0, 0"
Margin="10,2,10,-1"
AcceptsReturn="True"
VerticalAlignment="Center"
Text="{Binding QueryString}"
Foreground="{DynamicResource {ComponentResourceKey
TypeInTargetAssembly=dc:NavigationPane,
ResourceId={x:Static dc:NavigationPaneColors.NavPaneTitleForeground}}}">
</TextBox>
</StackPanel>
</StackPanel>
</Border>
</Border>
</DockPanel>
The TextBox control named "VerticallyExpandMe" needs to automatically expand vertically when the text bound to it does not fit on one line. With AcceptsReturn set to true, TextBox expands vertically if I press enter within it, but I want it do do this automatically.
Although Andre Luus's suggestion is basically correct, it won't actually work here, because your layout will defeat text wrapping. I'll explain why.
Fundamentally, the problem is this: text wrapping only does anything when an element's width is constrained, but your TextBox has unconstrained width because it's a descendant of a horizontal StackPanel. (Well, two horizontal stack panels. Possibly more, depending on the context from which you took your example.) Since the width is unconstrained, the TextBox has no idea when it is supposed to start wrapping, and so it will never wrap, even if you enable wrapping. You need to do two things: constrain its width and enable wrapping.
Here's a more detailed explanation.
Your example contains a lot of detail irrelevant to the problem. Here's a version I've trimmed down somewhat to make it easier to explain what's wrong:
<StackPanel Orientation="Horizontal">
<TextBlock Name="DataGridTitle" />
<StackPanel
Margin="5,0"
Orientation="Horizontal"
>
<TextBlock />
<TextBox
Name="VerticallyExpandMe"
Margin="10,2,10,-1"
AcceptsReturn="True"
VerticalAlignment="Center"
Text="{Binding QueryString}"
>
</TextBox>
</StackPanel>
</StackPanel>
So I've removed your containing DockPanel and the two nested Border elements inside of that, because they're neither part of the problem nor relevant to the solution. So I'm starting at the pair of nested StackPanel elements in your example. And I've also removed most of the attributes because most of them are also not relevant to the layout.
This looks a bit weird - having two nested horizontal stack panels like this looks redundant, but it does actually make sense in your original if you need to make the nested one visible or invisible at runtime. But it makes it easier to see the problem.
(The empty TextBlock tag is also weird, but that's exactly as it appears in your original. That doesn't appear to be doing anything useful.)
And here's the problem: your TextBox is inside some horizontal StackPanel elements, meaning its width is unconstrained - you have inadvertently told the text box that it is free to grow to any width, regardless of how much space is actually available.
A StackPanel will always perform layout that is unconstrained in the direction of stacking. So when it comes to lay out that TextBox, it'll pass in a horizontal size of double.PositiveInfinity to the TextBox. So the TextBox will always think it has more space than it needs. Moreover, when a child of a StackPanel asks for more space than is actually available, the StackPanel lies, and pretends to give it that much space, but then crops it.
(This is the price you pay for the extreme simplicity of StackPanel - it's simple to the point of being bone-headed, because it will happily construct layouts that don't actually fit. You should only use StackPanel if either you really do have unlimited space because you're inside a ScrollViewer, or you are certain that you have sufficiently few items that you're not going to run out of space, or if you don't care about items running off the end of the panel when they get too large and you don't want the layout system to try to do anything more clever than simply cropping the content.)
So turning on text wrapping won't help here, because the StackPanel will always pretend that there's more than enough space for the text.
You need a different layout structure. Stack panels are the wrong thing to use because they will not impose the layout constraint you need to get text wrapping to kick in.
Here's a simple example that does roughly what you want:
<Grid VerticalAlignment="Top">
<DockPanel>
<TextBlock
x:Name="DataGridTitle"
VerticalAlignment="Top"
DockPanel.Dock="Left"
/>
<TextBox
Name="VerticallyExpandMe"
AcceptsReturn="True"
TextWrapping="Wrap"
Text="{Binding QueryString}"
>
</TextBox>
</DockPanel>
</Grid>
If you create a brand new WPF application and paste that in as the content of the main window, you should find it does what you want - the TextBox starts out one line tall, fills the available width, and if you type text in, it'll grow one line at a time as you add more text.
Of course, layout behaviour is always sensitive to context, so it may not be enough to just throw that into the middle of your existing application. That will work if pasted into a fixed-size space (e.g. as the body of a window), but will not work correctly if you paste it into a context where width is unconstrained. (E.g., inside a ScrollViewer, or inside a horizontal StackPanel.)
So if this doesn't work for you, it'll be because of other things wrong elsewhere in your layout - possibly yet more StackPanel elements elsewhere. From the look of your example, it's probably worth spending some time thinking about what you really need in your layout and simplifying it - the presence of negative margins, and elements that don't appear to do anything like that empty TextBlock are usually indicative of an over-complicated layout. And unnecessary complexity in a layout makes it much hard to achieve the effects you're looking for.
Alternatively, you could constrain your TextBlock's Width by binding it to a parent's ActualWidth, for example:
<TextBlock Width="{Binding ElementName=*ParentElement*, Path=ActualWidth}"
Height="Auto" />
This will force it to resize its height automatically too.
Use MaxWidth and TextWrapping="WrapWithOverflow".
I'm using another simple approach that allows me not to change the document layout.
The main idea is not to set the control Width before it starts changing. For TextBoxes, I handle the SizeChanged event:
<TextBox TextWrapping="Wrap" SizeChanged="TextBox_SizeChanged" />
private void TextBox_SizeChanged(object sender, SizeChangedEventArgs e)
{
FrameworkElement box = (FrameworkElement)sender;
if (e.PreviousSize.Width == 0 || box.Width < e.PreviousSize.Width)
return;
box.Width = e.PreviousSize.Width;
}
You can use this class which extends TextBlock. It does auto-shrinking and takes MaxHeight / MaxWidth into consideration:
public class TextBlockAutoShrink : TextBlock
{
private double _defaultMargin = 6;
private Typeface _typeface;
static TextBlockAutoShrink()
{
TextBlock.TextProperty.OverrideMetadata(typeof(TextBlockAutoShrink), new FrameworkPropertyMetadata(new PropertyChangedCallback(TextPropertyChanged)));
}
public TextBlockAutoShrink() : base()
{
_typeface = new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch, this.FontFamily);
base.DataContextChanged += new DependencyPropertyChangedEventHandler(TextBlockAutoShrink_DataContextChanged);
}
private static void TextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var t = sender as TextBlockAutoShrink;
if (t != null)
{
t.FitSize();
}
}
void TextBlockAutoShrink_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
FitSize();
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
FitSize();
base.OnRenderSizeChanged(sizeInfo);
}
private void FitSize()
{
FrameworkElement parent = this.Parent as FrameworkElement;
if (parent != null)
{
var targetWidthSize = this.FontSize;
var targetHeightSize = this.FontSize;
var maxWidth = double.IsInfinity(this.MaxWidth) ? parent.ActualWidth : this.MaxWidth;
var maxHeight = double.IsInfinity(this.MaxHeight) ? parent.ActualHeight : this.MaxHeight;
if (this.ActualWidth > maxWidth)
{
targetWidthSize = (double)(this.FontSize * (maxWidth / (this.ActualWidth + _defaultMargin)));
}
if (this.ActualHeight > maxHeight)
{
var ratio = maxHeight / (this.ActualHeight);
// Normalize due to Height miscalculation. We do it step by step repeatedly until the requested height is reached. Once the fontsize is changed, this event is re-raised
// And the ActualHeight is lowered a bit more until it doesnt enter the enclosing If block.
ratio = (1 - ratio > 0.04) ? Math.Sqrt(ratio) : ratio;
targetHeightSize = (double)(this.FontSize * ratio);
}
this.FontSize = Math.Min(targetWidthSize, targetHeightSize);
}
}
}

WPF custom control size override

I'm creating a custom WPF control which derives from Grid.
It's a Chess board and so I want it's width to be the same as it's height.
How can I accomplish this?
Edit:
I tried the following.
private void cbcBoard_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Width != e.NewSize.Height)
{
double m = Math.Min(e.NewSize.Width, e.NewSize.Height);
cbcBoard.Width = m;
cbcBoard.Height = m;
}
}
It didn't work. Any ideas?
Thanks.
New solution/workaround. The UserControl can stay as it is, we'll leave the scaling to the parent container.
We can't accomplish Min(Width, Height) with just the UserControl because if we set the Height for it, then the parent container won't scale it Verticaly and the same goes for Width. If we try to juggle them around then there are situations where we end in an endless Width/Height resizing loop.
What we need is another hidden control in the same space that fills it completely and can tell us what its Width and Height is everytime it changes. Then we can use the Math.Min(Width, Height) solution. Something like this. Notice how both controls are in Grid.Row="1" and Grid.Column="1".
<Rectangle Name="availableSpace"
SizeChanged="availableSpace_SizeChanged"
Fill="Transparent"
Grid.Row="1"
Grid.Column="1"/>
<myLib:UserControl1 x:Name="userControl11"
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"/>
And then in the availableSpace_SizeChanged EventHandler
private void availableSpace_SizeChanged(object sender, SizeChangedEventArgs e)
{
double minValue = Math.Min(availableSpace.ActualWidth, availableSpace.ActualHeight);
userControl1.Width = minValue;
userControl1.Height = minValue;
}
Now we have 1:1 ratio of the UserControl and it will scale both Vertically and Horizontally
I think the simplest solution is to set the size of your UserControl to some constant value where Height == Width, and then drop the whole thing in a Viewbox which will handle the scaling (whilst maintaining aspect ratio) for you:
<Viewbox>
<myLib:UserControl1 x:Name="userControl11" />
</Viewbox>
This does mean that your control will be 'stretched' rather than being resized, which might make it inappropriate for you, but it is easy to implement!

Resources