Show text column wise in textbox in WPF - wpf

Is there any way that i can show my text in column wise like:
1 5
2 6
3 7
4 8
Like this:
<TextBox x:Name="mytextbox" TextWrapping="Wrap" AcceptsReturn="True" ScrollViewer.CanContentScroll="True" ScrollViewer.HorizontalScrollBarVisibility="Auto" />
This is my TextBox. I am getting text from a service which is string and set in this text box well that is not an issue. Issue is how can i display multiple text in column wise as I mention above?

<TextBox x:Name="Mytextbox" TextWrapping="Wrap" AcceptsReturn="True" AcceptsTab="True" ScrollViewer.CanContentScroll="True" ScrollViewer.HorizontalScrollBarVisibility="Auto"
Text="1 5
2 6
3 7
4 8">
</TextBox>
Also you can specify text in code like in the example:
Mytextbox.Text = "1\t3" + Environment.NewLine + "2\t4";

#omeriqbal: to explain my comment. create a collection from your string
this.MyText = new List<string>{"1","2","3","4",...};
instead of the textbox you could use an itemscontrol
<ItemsControl itemsSource="{Binding MyText}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Local:UniformGrid2 Orientation="Vertical" Rows="6" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
if you want a fix rowsize you can create you own uniformgrid
public class UniformGrid2 : UniformGrid
{
private int _columns;
private int _rows;
#region Orientation
/// <summary>
/// Orientation Dependency Property
/// </summary>
public static readonly DependencyProperty OrientationProperty =
StackPanel.OrientationProperty.AddOwner(typeof(UniformGrid2),
new FrameworkPropertyMetadata((Orientation)Orientation.Horizontal,
FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// Gets or sets the Orientation property.
/// </summary>
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
#endregion
protected override Size ArrangeOverride(Size arrangeSize)
{
if (Orientation == Orientation.Horizontal)
return base.ArrangeOverride(arrangeSize);
else
return ArrangeOverrideVertical(arrangeSize);
}
protected override Size MeasureOverride(Size constraint)
{
if (Orientation == Orientation.Horizontal)
return base.MeasureOverride(constraint);
else
return MeasureOverrideVertical(constraint);
}
private Size ArrangeOverrideVertical(Size arrangeSize)
{
Rect finalRect = new Rect(0.0, 0.0, arrangeSize.Width / ((double)_columns), arrangeSize.Height / ((double)_rows));
double height = finalRect.Height;
double totalHeight = arrangeSize.Height - 1.0;
foreach (UIElement element in base.InternalChildren)
{
element.Arrange(finalRect);
if (element.Visibility != Visibility.Collapsed)
{
finalRect.Y += height;
if (finalRect.Y >= totalHeight)
{
finalRect.X += finalRect.Width;
finalRect.Y = 0.0;
}
}
}
return arrangeSize;
}
private Size MeasureOverrideVertical(Size constraint)
{
UpdateComputedValuesVertical();
Size availableSize = new Size(constraint.Width / ((double)_columns), constraint.Height / ((double)_rows));
double width = 0.0;
double height = 0.0;
int i = 0;
int count = base.InternalChildren.Count;
while (i < count)
{
UIElement element = base.InternalChildren[i];
element.Measure(availableSize);
Size desiredSize = element.DesiredSize;
if (width < desiredSize.Width)
{
width = desiredSize.Width;
}
if (height < desiredSize.Height)
{
height = desiredSize.Height;
}
i++;
}
return new Size(width * _columns, height * _rows);
}
private void UpdateComputedValuesVertical()
{
_columns = Columns;
_rows = Rows;
// Ignore FirstColumn property
FirstColumn = 0;
if (_rows == 0 || _columns == 0)
{
int visibleChildren = 0;
int i = 0;
int count = base.InternalChildren.Count;
while (i < count)
{
UIElement element = base.InternalChildren[i];
if (element.Visibility != Visibility.Collapsed)
{
visibleChildren++;
}
i++;
}
if (visibleChildren == 0)
{
visibleChildren = 1;
}
if (_columns == 0)
{
if (_rows > 0)
{
_columns = (visibleChildren + (_rows - 1)) / _rows;
}
else
{
_columns = (int)Math.Sqrt((double)visibleChildren);
if ((_columns * _columns) < visibleChildren)
{
_columns++;
}
_rows = _columns;
}
}
else if (_rows == 0)
{
_rows = (visibleChildren + (_columns - 1)) / _columns;
}
}
}
}

There are AcceptsTab boolean property in Textbox allow you to have tab in string and when you want to type in a Textbox and press the tab key the Textbox focus don't lost and in the text the tab space appear and after saving it to database or variable and assign it to the Textbox you have your tabs.
<TextBox TextWrapping="Wrap" AcceptsReturn="True" AcceptsTab="True" VerticalScrollBarVisibility="Auto" Height="70"/>

Related

Wpf Decimals Styling

I want to Style a TextBox with decimal places like this:
How can I do that ?
You can extend the TextBox like this.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
public class DecimalTextBox : TextBox
{
public static readonly DependencyProperty FloatColorProperty = DependencyProperty.Register("FloatColor", typeof(Color), typeof(DecimalTextBox), new FrameworkPropertyMetadata(Colors.Red));
public Color FloatColor
{
get { return (Color)GetValue(FloatColorProperty); }
set { SetValue(FloatColorProperty, value); }
}
protected TextBlock _textBlock;
protected FrameworkElement _textBoxView;
public DecimalTextBox()
{
_textBlock = new TextBlock() { Margin = new Thickness(1, 0, 0, 0) };
Loaded += ExTextBox_Loaded;
}
private void ExTextBox_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= ExTextBox_Loaded;
// hide the original drawing visuals, by setting opacity on their parent
var visual = this.GetChildOfType<DrawingVisual>();
_textBoxView = (FrameworkElement)visual.Parent;
_textBoxView.Opacity = 0;
// add textblock to do the text drawing for us
var grid = this.GetChildOfType<Grid>();
if (grid.Children.Count >= 2)
grid.Children.Insert(1, _textBlock);
else
grid.Children.Add(_textBlock);
}
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnLostKeyboardFocus(e);
_textBoxView.Opacity = 0;
_textBlock.Visibility = Visibility.Visible;
}
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnGotKeyboardFocus(e);
_textBoxView.Opacity = 1;
_textBlock.Visibility = Visibility.Collapsed;
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
// making sure text on TextBlock is updated as per TextBox
var dotPos = Text.IndexOf('.');
var textPart1 = dotPos == -1 ? Text : Text.Substring(0, dotPos + 1);
var textPart2 = (dotPos == -1 || dotPos >= (Text.Length-1)) ? null : Text.Substring(dotPos + 1);
_textBlock.Inlines.Clear();
_textBlock.Inlines.Add(new Run {
Text = textPart1,
FontFamily = FontFamily,
FontSize = FontSize,
Foreground = Foreground });
if (textPart2 != null)
_textBlock.Inlines.Add(new Run {
Text = textPart2,
FontFamily = FontFamily,
TextDecorations = System.Windows.TextDecorations.Underline,
BaselineAlignment = BaselineAlignment.TextTop,
FontSize = FontSize * 5/6,
Foreground = new SolidColorBrush(FloatColor) });
}
}
public static class HelperExtensions
{
public static T GetChildOfType<T>(this DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
}
XAML code usage
<local:DecimalTextBox FloatColor="Maroon" />
And your output should look like this:
Update 05/17
Explanation: As you can see from the image, the DecimalTextBox displays the text in formatted mode only when its not focused.
I had initially developed the control to support formatting during edit (which can still be done by commenting the methods OnLostKeyboardFocus, and OnGotKeyboardFocus) - but because of the font-size difference the cursor positioning was getting slightly skewed, which in turn would translate to bad user experience.
Therefore, implemented the swap logic during GotFocus and LostFocus to fix that.
You can't do that with a TextBox, because TextBox only accepts color changes to the entire text. You should try with RichTextBox, that allows loop throug TextRange's. Look at this sample of syntax highlighting with a RichTextBox.
I actually understood how it works and made it:
private void richTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
richTextBox.TextChanged -= this.richTextBox_TextChanged;
if (richTextBox.Document == null)
return;
TextRange documentRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
documentRange.ClearAllProperties();
int dotIndex = documentRange.Text.IndexOf(".");
if(dotIndex == -1)
{
richTextBox.TextChanged += this.richTextBox_TextChanged;
return;
}
TextPointer dotStart = GetPoint(richTextBox.Document.ContentStart, dotIndex);
TextPointer dotEnd = dotStart.GetPositionAtOffset(1, LogicalDirection.Forward);
TextRange initRange = new TextRange(richTextBox.Document.ContentStart, dotStart);
TextRange endRange = new TextRange(dotEnd, richTextBox.Document.ContentEnd);
endRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Red));
richTextBox.TextChanged += this.richTextBox_TextChanged;
}
Suscribe the textbox TextChanged event to this method. You can now set the styles you want to every part of the text like this:
To change the last part (after the dot char) endRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Red));
To change the first part (before the dot char) is the same but for the initRange variable. If you want to change the 'dot' style, then create a new TextRange with dotStart and dotEnd TextPointers and apply styles to it. You can do other things like change font style, size, etc.
This code result looks like this:
All this is just for style. For checking that is a number is up to you.
I would define a custom control with this main properties:
FloatNumber: the original number, that should be a DependencyProperty to be Bind from the control.
NumberOfDecimalDigits: a number to choose how many decimal digits to represent, that should be a DependencyProperty to be Bind from the control.
FirstPart: a string that will contain the first part of the decimal number
Decimals: a string that will contain the decimal digits of FloatNumber
Of course this is just a scratch, those properties could be implemented better to extract FloatNumber parts.
public partial class DecimalDisplayControl : UserControl, INotifyPropertyChanged
{
public DecimalDisplayControl()
{
InitializeComponent();
(Content as FrameworkElement).DataContext = this;
}
public static readonly DependencyProperty NumberOfDecimalDigitsProperty =
DependencyProperty.Register(
"NumberOfDecimalDigits", typeof(string),
typeof(DecimalDisplayControl), new PropertyMetadata(default(string), OnFloatNumberChanged));
public static readonly DependencyProperty FloatNumberProperty =
DependencyProperty.Register(
"FloatNumber", typeof(string),
typeof(DecimalDisplayControl), new PropertyMetadata(default(string), OnFloatNumberChanged));
private static void OnFloatNumberChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as DecimalDisplayControl).OnFloatNumberChanged();
}
protected void OnFloatNumberChanged()
{
int numberOfDecimalDigits = Convert.ToInt32(NumberOfDecimalDigits);
float fullNumber = Convert.ToSingle(FloatNumber);
float firstPart = (float)Math.Truncate(fullNumber);
float fullDecimalPart = fullNumber - firstPart;
int desideredDecimalPart = (int)(fullDecimalPart * Math.Pow(10, numberOfDecimalDigits));
FirstPart = $"{firstPart}.";
Decimals = desideredDecimalPart.ToString();
}
public string FloatNumber
{
get => (string)GetValue(FloatNumberProperty);
set { SetValue(FloatNumberProperty, value); }
}
public string NumberOfDecimalDigits
{
get => (string)GetValue(NumberOfDecimalDigitsProperty);
set { SetValue(NumberOfDecimalDigitsProperty, value); }
}
private string _firstPart;
public string FirstPart
{
get => _firstPart;
set
{
if (_firstPart == value)
return;
_firstPart = value;
OnPropertyChanged();
}
}
private string _decimals;
public string Decimals
{
get => _decimals;
set
{
if (_decimals == value)
return;
_decimals = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Its XAML:
<UserControl
x:Class="WpfApp1.CustomControls.DecimalDisplayControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
FontSize="20"
Foreground="Black"
Text="{Binding FirstPart}" />
<TextBlock
FontSize="10"
Foreground="Red"
Text="{Binding Decimals}"
TextDecorations="Underline" />
</StackPanel>
</UserControl>
Then you can use it in your page and bind a property to make it change dynamically:
<Grid>
<StackPanel VerticalAlignment="Center" Orientation="Vertical">
<customControls:DecimalDisplayControl
HorizontalAlignment="Center"
VerticalAlignment="Center"
NumberOfDecimalDigits="2"
FloatNumber="{Binding MyNumber}" />
<TextBox
Width="200"
VerticalAlignment="Center"
Text="{Binding MyNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Grid>
The final result:

How can i create a 2x2 grid in WPF that resizes when elements change visibility

I'm trying to create a grid with two columns and two rows in WPF. I want the grid to resize in a special kind of way depending on which cells are visible, I've made some illustrations to better explain:
Case 1: One item is visible - one cell fills all available space
Case 2; Two items are visible - space is divided between rows
Case 3: Three items are visible - row 2 is split in two columns
Case 4: All four items are visible - Space is distributed evenly
This post: Resizing WPF Grid Column when other Columns children is collapsed?
Describes how to solve my problem for a single row using a visibility to size converter, but in order to make it work for two rows i would have to bind the size of one row to the visibility of two elements. This might be possible using a multibinding, but i'm not sure if that would be the best approach. Maybe there is a better way of doing this rather than using a grid at all?
Kind of a fun problem. Basically you want to encapsulate the notion of a movable item in a class so you can easily bind the Grid props to something programmable. In my thinking, I created a type Cell for this. Now you can bind all the props to one object when you want the layout moved around. You'll see 4 methods in the view-model: ShowAllCells, Show3Cells, etc. which are what you would call based on your presentation logic. Think this pretty much does it.
XAML
<Window x:Class="WpfApplication7.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:WpfApplication7"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
d:DataContext="{d:DesignInstance {x:Type local:MainWindowViewModel}}">
<Window.Resources>
<Style TargetType="Rectangle">
<Setter Property="Width" Value="50"/>
<Setter Property="Height" Value="50"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle Fill="Red" Grid.Row="{Binding Cell1.Row}" Grid.Column="{Binding Cell1.Col}" Grid.ColumnSpan="{Binding Cell1.ColSpan}" Grid.RowSpan="{Binding Cell1.RowSpan}" Visibility="{Binding Cell1.Vis}" />
<Rectangle Fill="Blue" Grid.Row="{Binding Cell2.Row}" Grid.Column="{Binding Cell2.Col}" Grid.ColumnSpan="{Binding Cell2.ColSpan}" Grid.RowSpan="{Binding Cell2.RowSpan}" Visibility="{Binding Cell2.Vis}" />
<Rectangle Fill="Yellow" Grid.Row="{Binding Cell3.Row}" Grid.Column="{Binding Cell3.Col}" Grid.ColumnSpan="{Binding Cell3.ColSpan}" Grid.RowSpan="{Binding Cell3.RowSpan}" Visibility="{Binding Cell3.Vis}" />
<Rectangle Fill="Green" Grid.Row="{Binding Cell4.Row}" Grid.Column="{Binding Cell4.Col}" Grid.ColumnSpan="{Binding Cell4.ColSpan}" Grid.RowSpan="{Binding Cell4.RowSpan}" Visibility="{Binding Cell4.Vis}" />
</Grid>
View-Model & Cell
using System.ComponentModel;
using System.Windows;
namespace WpfApplication7
{
public class Cell : INotifyPropertyChanged
{
private int row;
public int Row
{
get { return row; }
set
{
if (row == value) return;
row = value;
OnPropertyChanged(#"Row");
}
}
private int col;
public int Col
{
get { return col; }
set
{
if (col == value) return;
col = value;
OnPropertyChanged(#"Col");
}
}
private int rowSpan;
public int RowSpan
{
get { return rowSpan; }
set
{
if (rowSpan == value) return;
rowSpan = value;
OnPropertyChanged(#"RowSpan");
}
}
private int colSpan;
public int ColSpan
{
get { return colSpan; }
set
{
if (colSpan == value) return;
colSpan = value;
OnPropertyChanged(#"ColSpan");
}
}
private Visibility vis;
public Visibility Vis
{
get { return vis; }
set
{
if (vis == value) return;
vis = value;
OnPropertyChanged(#"Vis");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler == null) return;
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
#endregion
}
public class MainWindowViewModel
{
public MainWindowViewModel()
{
Cell1 = new Cell();
Cell2 = new Cell();
Cell3 = new Cell();
Cell4 = new Cell();
// Call the one you want.
ShowAllCells();
Show3Cells(Cell2, Cell3, Cell4, Cell1);
Show2Cells(Cell2, Cell3, Cell4, Cell1);
Show1Cell(Cell2, Cell3, Cell4, Cell1);
}
private void ShowAllCells()
{
Cell1.Row = 0;
Cell1.Col = 0;
Cell1.RowSpan = 1;
Cell1.ColSpan = 1;
Cell1.Vis = Visibility.Visible;
Cell2.Row = 0;
Cell2.Col = 1;
Cell2.RowSpan = 1;
Cell2.ColSpan = 1;
Cell2.Vis = Visibility.Visible;
Cell3.Row = 1;
Cell3.Col = 0;
Cell3.RowSpan = 1;
Cell3.ColSpan = 1;
Cell3.Vis = Visibility.Visible;
Cell4.Row = 1;
Cell4.Col = 1;
Cell4.RowSpan = 1;
Cell4.ColSpan = 1;
Cell4.Vis = Visibility.Visible;
}
private void Show3Cells(Cell one, Cell two, Cell three, Cell hidden1)
{
one.Row = 0;
one.Col = 0;
one.RowSpan = 1;
one.ColSpan = 2;
one.Vis = Visibility.Visible;
two.Row = 1;
two.Col = 0;
two.RowSpan = 1;
two.ColSpan = 1;
two.Vis = Visibility.Visible;
three.Row = 1;
three.Col = 1;
three.RowSpan = 1;
three.ColSpan = 1;
three.Vis = Visibility.Visible;
hidden1.Row = 0;
hidden1.Col = 0;
hidden1.RowSpan = 1;
hidden1.ColSpan = 1;
hidden1.Vis = Visibility.Collapsed;
}
private void Show2Cells(Cell one, Cell two, Cell hidden1, Cell hidden2)
{
one.Row = 0;
one.Col = 0;
one.RowSpan = 1;
one.ColSpan = 2;
one.Vis = Visibility.Visible;
two.Row = 1;
two.Col = 0;
two.RowSpan = 1;
two.ColSpan = 2;
two.Vis = Visibility.Visible;
hidden1.Row = 0;
hidden1.Col = 0;
hidden1.RowSpan = 1;
hidden1.ColSpan = 1;
hidden1.Vis = Visibility.Collapsed;
hidden2.Row = 0;
hidden2.Col = 0;
hidden2.RowSpan = 1;
hidden2.ColSpan = 1;
hidden2.Vis = Visibility.Collapsed;
}
private void Show1Cell(Cell one, Cell hidden1, Cell hidden2, Cell hidden3)
{
one.Row = 0;
one.Col = 0;
one.RowSpan = 2;
one.ColSpan = 2;
one.Vis = Visibility.Visible;
hidden1.Row = 0;
hidden1.Col = 0;
hidden1.RowSpan = 1;
hidden1.ColSpan = 1;
hidden1.Vis = Visibility.Collapsed;
hidden2.Row = 0;
hidden2.Col = 0;
hidden2.RowSpan = 1;
hidden2.ColSpan = 1;
hidden2.Vis = Visibility.Collapsed;
hidden3.Row = 0;
hidden3.Col = 0;
hidden3.RowSpan = 1;
hidden3.ColSpan = 1;
hidden3.Vis = Visibility.Collapsed;
}
public Cell Cell1 { get; set; }
public Cell Cell2 { get; set; }
public Cell Cell3 { get; set; }
public Cell Cell4 { get; set; }
}
}

Make wpf button fonts match at runtime

I have several buttons, like this:
<Button Name="btnContent" Grid.Column="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
<Viewbox MaxWidth="100" StretchDirection="Both">
<TextBlock Text="Content" ></TextBlock>
</Viewbox>
</Button>
<Button Name="btnMoreContent" Grid.Column="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
<Viewbox MaxWidth="100" StretchDirection="Both">
<TextBlock Text="More Content" ></TextBlock>
</Viewbox>
</Button>
The font scales to fit the buttons. How can I grab the text size / scale value of the button with the smallest text at runtime and set them all to that size?
I tried:
double FontSize = btnContentButton.FontSize;
if (FontSize < btnLongerContentButton.FontSize)
{
FontSize = btnLongerContentButton.FontSize;
}
btnContentButton.FontSize = FontSize;
btnLongerContentButton.FontSize = FontSize;
But this doesn't work, because I never actually changed the font size - It sets them all to 12.
Try the suggested solutions in this link and see if they can solve the problem.
However, you might want to try this one too: Handle Load event, find all TextBlocks with parent of type ViewBox (You might check other conditions or set proper names, to avoid further problems here), adjust their Width, properly, based on the Width of the one with the longest Text:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
List<TextBlock> tbs = new List<TextBlock>();
TextBlock longestText = null;
foreach (TextBlock t in FindVisualChildren<TextBlock>(this))
{
if (t.Parent is Viewbox)
{
tbs.Add(t);
if (longestText == null)
longestText = t;
else if (t.Text.Length > longestText.Text.Length)
longestText = t;
}
}
double a = longestText.ActualWidth / longestText.ActualHeight;
foreach (TextBlock tb in tbs)
{
if (tb == longestText)
continue;
tb.Width = a * tb.ActualHeight;
}
}
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
FindVisualChildren method is from here.
Hope it helps.

Listbox - Size to content until Max number of rows is met

I'd like to have a ListBox that sizes to its content, until some MaxRow property is met. So, if the MaxRow value was 3, it would behave in the following manner.
Items.Count == 0 -> SizeToContent
Items.Count == 1 -> SizeToContent
Items.Count == 2 -> SizeToContent
Items.Count == 3 -> SizeToContent
Items.Count == 4 -> limit height to 3 rows and enable scrollbar
Items.Count == 5 -> limit height to 3 rows and enable scrollbar
etc etc
I thought the correct way to do this would be to use a custom Panel (as seen below), but this doesn't seem to work.
How could I achieve this?
<ListBox ItemsSource="{Binding Items}"
HorizontalContentAlignment="Stretch">
<ListBox.Template>
<ControlTemplate>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<l:LimitingStackPanel VerticalAlignment="Top"
IsItemsHost="True"/>
</ScrollViewer>
</ControlTemplate>
</ListBox.Template>
</ListBox>
public class LimitingStackPanel : Panel
{
protected override Size MeasureOverride(Size availableSize)
{
Size measuredSize = new Size(0, 0);
int count = 0;
foreach(UIElement item in InternalChildren)
{
item.Measure(availableSize);
measuredSize.Width = Math.Max(measuredSize.Width, item.DesiredSize.Width);
if(++count <= 4)
{
measuredSize.Height += item.DesiredSize.Height;
}
}
return measuredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
double y = 0;
foreach (UIElement item in InternalChildren)
{
double height = item.DesiredSize.Height;
item.Arrange(new Rect(0, y, finalSize.Width, height));
y += height;
}
return new Size(finalSize.Width, y);
}
}
Edit : Here is an example of what I'm trying to achieve
Edit : The is the solution I used in the end (based on the answer from PushPraj)
<DockPanel>
<UniformGrid Columns="2"
DockPanel.Dock="Top">
<Button Content="Add"
Click="OnAddClick" />
<Button Content="Remove"
Click="OnRemoveClick" />
</UniformGrid>
<StackPanel>
<ListBox l:ListBoxHelper.AutoSizeItemCount="3"
ItemsSource="{Binding ItemsA}" />
<ListBox l:ListBoxHelper.AutoSizeItemCount="3"
ItemsSource="{Binding ItemsB}" />
<ListBox l:ListBoxHelper.AutoSizeItemCount="3"
ItemsSource="{Binding ItemsC}" />
<ListBox l:ListBoxHelper.AutoSizeItemCount="3"
ItemsSource="{Binding ItemsD}" />
<ListBox l:ListBoxHelper.AutoSizeItemCount="3"
ItemsSource="{Binding ItemsE}" />
<ListBox l:ListBoxHelper.AutoSizeItemCount="3"
ItemsSource="{Binding ItemsF}" />
</StackPanel>
</DockPanel>
public class ListBoxHelper : DependencyObject
{
public static int GetAutoSizeItemCount(DependencyObject obj)
{
return (int)obj.GetValue(AutoSizeItemCountProperty);
}
public static void SetAutoSizeItemCount(DependencyObject obj, int value)
{
obj.SetValue(AutoSizeItemCountProperty, value);
}
public static readonly DependencyProperty AutoSizeItemCountProperty =
DependencyProperty.RegisterAttached("AutoSizeItemCount", typeof(int), typeof(ListBoxHelper), new PropertyMetadata(0, OnAutoSizeItemCountChanged));
static void OnAutoSizeItemCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listBox = d as ListBox;
// we set this to 0.0 so that we ddon't create any elements
// before we have had a chance to modify the scrollviewer
listBox.MaxHeight = 0.0;
listBox.Loaded += OnListBoxLoaded;
}
static void OnListBoxLoaded(object sender, RoutedEventArgs e)
{
var listBox = sender as ListBox;
var sv = Helper.GetChildOfType<ScrollViewer>(listBox);
if(sv != null)
{
// limit the scrollviewer height so that the bare minimum elements are generated
sv.MaxHeight = 1.0;
var vsp = Helper.GetChildOfType<VirtualizingStackPanel>(listBox);
if(vsp != null)
{
vsp.SizeChanged += OnVirtualizingStackPanelSizeChanged;
}
}
listBox.MaxHeight = double.PositiveInfinity;
}
static void OnVirtualizingStackPanelSizeChanged(object sender, SizeChangedEventArgs e)
{
var vsp = sender as VirtualizingStackPanel;
var lb = (ListBox)ItemsControl.GetItemsOwner(vsp);
int maxCount = GetAutoSizeItemCount(lb);
vsp.ScrollOwner.MaxHeight = vsp.Children.Count == 0 ? 1 : ((FrameworkElement)vsp.Children[0]).ActualHeight * maxCount;
}
}
public static class Helper
{
public static T GetChildOfType<T>(this DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
}
public partial class MainWindow : Window
{
public ObservableCollection<string> ItemsA { get; private set; }
public ObservableCollection<string> ItemsB { get; private set; }
public ObservableCollection<string> ItemsC { get; private set; }
public ObservableCollection<string> ItemsD { get; private set; }
public ObservableCollection<string> ItemsE { get; private set; }
public ObservableCollection<string> ItemsF { get; private set; }
public MainWindow()
{
ItemsA = new ObservableCollection<string>(Enumerable.Repeat("Word", 0));
ItemsB = new ObservableCollection<string>(Enumerable.Repeat("Word", 1));
ItemsC = new ObservableCollection<string>(Enumerable.Repeat("Word", 2));
ItemsD = new ObservableCollection<string>(Enumerable.Repeat("Word", 3));
ItemsE = new ObservableCollection<string>(Enumerable.Repeat("Word", 4));
ItemsF = new ObservableCollection<string>(Enumerable.Repeat("Word", 1000000));
DataContext = this;
InitializeComponent();
}
void OnAddClick(object _sender, EventArgs _args)
{
ItemsA.Add("new");
ItemsB.Add("new");
ItemsC.Add("new");
ItemsD.Add("new");
ItemsE.Add("new");
ItemsF.Add("new");
}
void OnRemoveClick(object _sender, EventArgs _args)
{
ItemsA.Remove(ItemsA.LastOrDefault());
ItemsB.Remove(ItemsB.LastOrDefault());
ItemsC.Remove(ItemsC.LastOrDefault());
ItemsD.Remove(ItemsD.LastOrDefault());
ItemsE.Remove(ItemsE.LastOrDefault());
ItemsF.Remove(ItemsF.LastOrDefault());
}
}
I attempted to solve it via attached properties hence making this as behavior
sample xaml
<StackPanel xmlns:l="clr-namespace:CSharpWPF">
<ListBox l:ListBoxHelper.AutoSizeItemCount="3">
<ListBoxItem>item 1</ListBoxItem>
</ListBox>
<ListBox l:ListBoxHelper.AutoSizeItemCount="3">
<ListBoxItem>item 1</ListBoxItem>
<ListBoxItem>item 2</ListBoxItem>
</ListBox>
<ListBox l:ListBoxHelper.AutoSizeItemCount="3">
<ListBoxItem>item 1</ListBoxItem>
<ListBoxItem>item 2</ListBoxItem>
<ListBoxItem>item 3</ListBoxItem>
</ListBox>
<ListBox l:ListBoxHelper.AutoSizeItemCount="3">
<ListBoxItem>item 1</ListBoxItem>
<ListBoxItem>item 2</ListBoxItem>
<ListBoxItem>item 3</ListBoxItem>
<ListBoxItem>item 4</ListBoxItem>
</ListBox>
<ListBox l:ListBoxHelper.AutoSizeItemCount="3">
<ListBoxItem>item 1</ListBoxItem>
<ListBoxItem>item 2</ListBoxItem>
<ListBoxItem>item 3</ListBoxItem>
<ListBoxItem>item 4</ListBoxItem>
<ListBoxItem>item 5</ListBoxItem>
</ListBox>
</StackPanel>
note that I have added ListBoxHelper.AutoSizeItemCount="3" to ListBox. I have made this number flexible for easy adaptation for various scenarios
ListBoxHelper class
namespace CSharpWPF
{
public class ListBoxHelper : DependencyObject
{
public static int GetAutoSizeItemCount(DependencyObject obj)
{
return (int)obj.GetValue(AutoSizeItemCountProperty);
}
public static void SetAutoSizeItemCount(DependencyObject obj, int value)
{
obj.SetValue(AutoSizeItemCountProperty, value);
}
// Using a DependencyProperty as the backing store for AutoSizeItemCount. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoSizeItemCountProperty =
DependencyProperty.RegisterAttached("AutoSizeItemCount", typeof(int), typeof(ListBoxHelper), new PropertyMetadata(0, OnAutoSizeItemCountChanged));
private static void OnAutoSizeItemCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ListBox listBox = d as ListBox;
listBox.AddHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler((lb, arg) => UpdateSize(listBox)));
listBox.ItemContainerGenerator.ItemsChanged += (ig, arg) => UpdateSize(listBox);
}
static void UpdateSize(ListBox listBox)
{
ItemContainerGenerator gen = listBox.ItemContainerGenerator;
FrameworkElement element = listBox.InputHitTest(new Point(listBox.Padding.Left + 5, listBox.Padding.Top + 5)) as FrameworkElement;
if (element != null && gen != null)
{
object item = element.DataContext;
if (item != null)
{
FrameworkElement container = gen.ContainerFromItem(item) as FrameworkElement;
if (container == null)
{
container = element;
}
int maxCount = GetAutoSizeItemCount(listBox);
double newHeight = Math.Min(maxCount, gen.Items.Count) * container.ActualHeight;
newHeight += listBox.Padding.Top + listBox.Padding.Bottom + listBox.BorderThickness.Top + listBox.BorderThickness.Bottom + 2;
if (listBox.ActualHeight != newHeight)
listBox.Height = newHeight;
}
}
}
}
}
result is a auto-height ListBox up to the specified number of items.
this solution assumes same size for all the items. finally it is not a perfect solution for the problem I can say it is a workaround. also worth to note that it will only be able to re-size during run-time, at design time it is not effective.
give this a try and see how close is this with your needs.
if you know the item height in advance then a better solution would be using the MaxHeight property

Min and max binding

I have two text boxes: min and max for user to enter. If the max number is smaller than the min number, the number in the max text box will be automatically changed to the same number as the min number in the min text box.
What is the best way to implement it with wpf and C#? Code would be great.
Thanks!
Define two properties MinValue and MaxValue of type int in your ViewModel (if using MVVM) and bind to two text boxes.
C#
private int minValue;
private int maxValue;
public int MinValue
{
get { return minValue; }
set
{
minValue = value;
PropertyChanged(this, new PropertyChangedEventArgs("MinValue"));
if (minValue > maxValue)
{
MaxValue = minValue;
}
}
}
public int MaxValue
{
get { return maxValue; }
set
{
maxValue = value;
PropertyChanged(this, new PropertyChangedEventArgs("MaxValue"));
if(maxValue < minValue)
{
MinValue = maxValue;
}
}
}
Xaml:
<TextBox Text="{Binding MinValue, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding MaxValue, UpdateSourceTrigger=PropertyChanged}"/>
Thanks
Just putting the way I achieve in my WPF code for MIN numeric Value is 0 and MAX numeric value is 99.
hope this may help
<!-- WPF Code Sample.xaml -->
<TextBox
Text="1"
Width="20"
PreviewTextInput="PreviewTextInputHandler"
IsEnabled="True"/>
https://social.msdn.microsoft.com/Forums/vstudio/en-US/990c635a-c14e-4614-b7e6-65471b0e0e26/how-to-set-minvalue-and-max-value-for-a-testbox-in-wpf?forum=wpf
// C# Code Sample.xaml.cs
private void PreviewTextInputHandler(object sender, TextCompositionEventArgs e)
{
// MIN Value is 0 and MAX value is 99
var textBox = sender as TextBox;
bool bFlag = false;
if (!string.IsNullOrWhiteSpace(e.Text) && !string.IsNullOrWhiteSpace(textBox.Text))
{
string str = textBox.Text + e.Text;
bFlag = str.Length <= 2 ? false : true;
}
e.Handled = (Regex.IsMatch(e.Text, "[^0-9]+") || bFlag);
}

Resources