How to make the text in textblock display vertically in UWP/WinRT - c#

I need to change the display order of the text in my UWP app but unfortunately I don't find any straight solution to do so.
The textblock in WinRT does not support this property, at least I can't found any information about this feature from MSDN. I found a solution that I need create a "New" textblock control which supports the text display in vertical order but the solution is for silverlight so I'm working on it to see whether it works or not.
This is how textblock works normally:
This is how textblock that I want it to work:
I know there is a way that just setting up the Width and text wraping something but it only works for a certain screen size & resolution, which means under other screen the text will not display properly
Any tips would be appreciated.

To get a "real" vertical text in UWP try the following:
<TextBlock Text="Rotated Text"
FontSize="18"
Foreground="Black">
<TextBlock.RenderTransform>
<RotateTransform Angle="-90" />
</TextBlock.RenderTransform>
</TextBlock>

Edit - UWP verison with user control
VerticalTextBlock - code behind
public partial class VerticalTextBlock : UserControl
{
public VerticalTextBlock()
{
InitializeComponent();
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text",
typeof(string),
typeof(VerticalTextBlock),
new PropertyMetadata(string.Empty, textChangeHandler));
private static void textChangeHandler(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var prop = d as VerticalTextBlock;
var textBlock = prop.TheTextBlock;
var str = (e.NewValue as string);
textBlock.Inlines.Clear();
for (int i = 0; i < str.Length-1; i++)
{
textBlock.Inlines.Add(new Run() { Text = str[i] + Environment.NewLine });
}
textBlock.Inlines.Add(new Run() { Text = str[str.Length-1].ToString()});
}
}
VerticalTextBlock - XAML
<UserControl
...
>
<TextBlock x:Name="TheTextBlock"/>
</UserControl>
Usage and test - XAML
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock x:Name="a" Text="ASD"></TextBlock>
<local:VerticalTextBlock x:Name="b" Text="{Binding ElementName=a, Path=Text}" />
<local:VerticalTextBlock x:Name="c" Text="{Binding ElementName=b, Path=Text}" />
<TextBlock x:Name="d" Text="{Binding ElementName=c, Path=Text}"></TextBlock>
<TextBlock TextAlignment="Center" HorizontalAlignment="Left">
<Run Text="A"/>
<LineBreak/>
<Run Text="S"/>
<LineBreak/>
<Run Text="D"/>
<LineBreak/>
<Run Text="A"/>
<LineBreak/>
<Run Text="S"/>
<LineBreak/>
<Run Text="D"/>
</TextBlock>
</StackPanel>
Original Answer - didn't notice it's UWP not WPF
You got me interested as I've only done this in Android, so there are a few solutions that will work but I decided to try custom control extending TextBlock
public partial class VerticalTextBlock : TextBlock
{
public VerticalTextBlock()
{
InitializeComponent();
}
new public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
new public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text",
typeof(string),
typeof(VerticalTextBlock),
new PropertyMetadata(string.Empty, textChangeHandler));
private static void textChangeHandler(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var prop = d as VerticalTextBlock;
var str = (e.NewValue as string);
var inlines = str.Select(x => new Run(x + Environment.NewLine));
prop.Inlines.Clear();
prop.Inlines.AddRange(inlines);
}
}
Usage in XAML
<local:VerticalTextBlock Text="AABBCCDDEEFF" />

Related

RichTextBox Binding is broken when text is manually modified or cleared

I have a RichTextBox bound to a string.
Using C# I generate a string that writes to it.
But if I want to manually change the text by clicking into the RichTextBox and deleting it with the backspace key, or pressing Enter to make a new line, the binding becomes broken and I can no longer programmatically write to it with the string a second time.
XAML
<RichTextBox x:Name="rtbScriptView"
Margin="11,71,280,56"
Padding="10,10,10,48"
FontSize="14"
Grid.ColumnSpan="1"
VerticalScrollBarVisibility="Auto"
RenderOptions.ClearTypeHint="Enabled"
Style="{DynamicResource RichTextBoxStyle}">
<FlowDocument>
<Paragraph>
<Run Text="{Binding ScriptView_Text,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
</Paragraph>
</FlowDocument>
</RichTextBox>
View Model
private string _ScriptView_Text;
public string ScriptView_Text
{
get { return _ScriptView_Text; }
set
{
if (_ScriptView_Text == value)
{
return;
}
_ScriptView_Text = value;
OnPropertyChanged("ScriptView_Text");
}
}
C#
ViewModel vm = new ViewModel();
DataContext = vm;
// Display a string in the RichTextBox
vm.ScriptView_Text = "This is a test."; // <-- This won't work if text is manually modified
When you edit the RichTextBox, you alter the elements inside of the FlowDocument element. The element you have a binding on, is probably removed at some point during this editing.
Have a look at RichtTextBox.Document.Groups to see what's happening when you edit the RichTextBox.
The default RichTextBox does not really support MVVM/Binding very well. You'd want to have a binding on the Document property, but this is not supported for the default RichTextBox.
You could have a look here.
Or extend it yourself, something like this?:
BindableRichTextBox class
public class BindableRichTextBox : RichTextBox
{
public static readonly DependencyProperty DocumentProperty = DependencyProperty.Register(nameof(Document), typeof(FlowDocument), typeof(BindableRichTextBox), new FrameworkPropertyMetadata(null, OnDocumentChanged));
public new FlowDocument Document
{
get => (FlowDocument)GetValue(DocumentProperty);
set => SetValue(DocumentProperty, value);
}
public static void OnDocumentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var rtb = (RichTextBox)obj;
rtb.Document = args.NewValue != null ? (FlowDocument)args.NewValue : new FlowDocument();
}
}
XAML
<controls:BindableRichTextBox Document="{Binding YourFlowDocumentObject, Mode=OneWay}"/>
Then you can get the string from the FlowDocument.
Why you have to write this line. Please remove line after check.
if (_ScriptView_Text == value)
{
return;
}

UWP Menuflyout programatically doesn't trigger at run time

I have this XAML
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Left">
<Button
x:Name="BtnTiempo"
Content=""
Style="{StaticResource AppBaseButton}"
Padding="0"
FontSize="17"
Foreground="Red">
<Button.ContextFlyout>
<MenuFlyout x:Name="TiemposMnu">
<MenuFlyout.Items>
</MenuFlyout.Items>
</MenuFlyout>
</Button.ContextFlyout>
</Button>
<TextBlock Text="{Binding Tiempo.StrDescripcion,FallbackValue=?}" Grid.Column="1" TextAlignment="Right" Foreground="Red"/>
</StackPanel>
and this Code that fills TiemposMnu
#region Tiempos
public List<Tiempo> Tiempos
{
get { return (List<Tiempo>)GetValue(TiemposProperty); }
set { SetValue(TiemposProperty, value); }
}
// Using a DependencyProperty as the backing store for Tiempos. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TiemposProperty =
DependencyProperty.Register("Tiempos", typeof(List<Tiempo>), typeof(ItemDetallePedidoControl), new PropertyMetadata(null,new PropertyChangedCallback(OnTiemposChanged)));
private static void OnTiemposChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(d is ItemDetallePedidoControl p)
{
if (p.Tiempos != null)
{
foreach (var tiempo in p.Tiempos)
{
MenuFlyoutItem item = new MenuFlyoutItem()
{
Text = tiempo.StrDescripcion
};
item.Click += (s, e1) =>
{
p.SeleccionarTiempo(tiempo.IntIdTiempo);
};
p.TiemposMnu.Items.Add(item);
}
}
}
}
#endregion
Everything fires ok. But when I tap / click my button doesn't shows the MenuFlyout.
What I'm doing wrong ?
But when I tap / click my button doesn't shows the MenuFlyout.
If you want to tap/click the button to show MenuFlyout you need to use Button.Flyout . Details please see the remark section.
<Button.Flyout>
<MenuFlyout x:Name="TiemposMnu">
<MenuFlyout.Items>
</MenuFlyout.Items>
</MenuFlyout>
</Button.Flyout>
If you want to trigger the MenuFlyout associate with Button.ContextFlyout, right-click (mouse) or press-and-hold (touch) directly on the button. Mode details please reference official sample.

Nesting a Label inside a ListView in XAML

I am pretty new to WPF and I have tried figuring out how to add a Label appear inside a the following ListView which shows the number of Items currently in the ListView. I've given the ListView padding on the top to make room for the Label.
<ListView x:Name="MyListView" Grid.Row="0" Grid.Column="0" Margin="0,40,0,0" Padding="0" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding DatasetCode}" FontWeight="Bold"/>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
If anyone can help me out, it would be greatly appreciated.
Edit the Template of ListBox. You can do this by Right-Clicking the ListBox in the Document outline section. And add your Label as below.
...
<ScrollViewer Focusable="false" Padding="{TemplateBinding Padding}">
<StackPanel>
<Label uc:Window2.CountFor="False" />
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</StackPanel>
</ScrollViewer>
...
I have written an attached property CountFor . Code is give below :
#region CountFor attached property
public static bool GetCountFor(DependencyObject obj)
{
return (bool)obj.GetValue(CountForProperty);
}
public static void SetCountFor(DependencyObject obj, bool value)
{
obj.SetValue(CountForProperty, value);
}
// Using a DependencyProperty as the backing store for CountFor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CountForProperty =
DependencyProperty.RegisterAttached("CountFor", typeof(bool), typeof(Window2), new PropertyMetadata(false, new PropertyChangedCallback(GetCountForChanged)));
private static void GetCountForChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue == false) return;
Label lbl = (Label)d;
lbl.Loaded += (o, args) =>
{
DependencyObject parent = VisualTreeHelper.GetParent(lbl);
while (parent.GetType() != typeof(ListBox))
parent = VisualTreeHelper.GetParent(parent);
ListBox lb = (ListBox)parent;
ICollectionView view = CollectionViewSource.GetDefaultView(lb.ItemsSource);
lbl.Content = "Number of items = " + ((ListCollectionView)view).Count;
view.CollectionChanged += (col, colargs) =>
{
lbl.Content = "Number of items = " + ((ListCollectionView)col).Count;
System.Diagnostics.Debug.WriteLine(((ListCollectionView)col).Count.ToString());
};
};
}
#endregion
Your solution is simple, you could just create an int to count the number of items in your label and then assign a new textblock, you could also completely skip the textblock and simply add the int, check this code:
private void button1_Click(object sender, RoutedEventArgs e)
{
int testcounter;
testcounter = listBox.Items.Count;
TextBlock BlockCounter = new TextBlock();
BlockCounter.Text = testcounter.ToString();
listBox.Items.Add(BlockCounter);
}

WPF databinding a user control

Ive been looking for hours on a solution to this.
I have a tab Adapter class that im using to fill a tab control
public partial class TabAdapter : UserControl
{
public static readonly DependencyProperty fileNameProperty =
DependencyProperty.Register(
"fileName",
typeof(string),
typeof(TabAdapter),
new FrameworkPropertyMetadata(
string.Empty,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnFileNamePropertyChanged),
new CoerceValueCallback(coerceFileName)
),
new ValidateValueCallback(fileNameValidationCallback)
);
public TabAdapter()
{
InitializeComponent();
//initializeInterior();
CreateSaveCommand();
TabAdapterContent.DataContext = this;
Console.WriteLine("constructor hit.");
}
public string fileName
{
get { return (string)GetValue(fileNameProperty); }
set { SetValue(fileNameProperty, value); }
}
private ColumnMapper _columnMap;
private TableMapper _tableMap;
private TabType tabType;
private enum TabType { TABLE_MAPPER, COLUMN_MAPPER, ERROR_MSG }
private static object coerceFileName(DependencyObject d, object value)
{
return fileName;
}
private static bool fileNameValidationCallback(object Value)
{
string fn = (string)Value;
if (fn.Equals(string.Empty))
{
return true;
}
FileInfo fi = new FileInfo(fn);
return ((fi.Exists && fi.Extension.Equals(".csv")));
}
private static void OnFileNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
TabAdapter source = d as TabAdapter;
Console.WriteLine("got to property changer: " + (string)args.NewValue + " :new / old: " + (string)args.OldValue);
source.initializeInterior();
}
private void initializeInterior()
{
Console.WriteLine("initializing Interior filename: " + fileName);
if (Regex.IsMatch(fileName, #".*_SourceTableMapping.csv$"))
{
tabType = TabType.TABLE_MAPPER;
_tableMap = new TableMapper(fileName);
Grid.SetRow(_tableMap, 0);
Grid.SetColumn(_tableMap, 0);
//clear out the content.
this.TabAdapterContent.Children.Clear();
//add new content
this.TabAdapterContent.Children.Add(_tableMap);
}
else if (fileName.EndsWith(".csv"))
{
tabType = TabType.TABLE_MAPPER;
_columnMap = new ColumnMapper(fileName);
Grid.SetRow(_columnMap, 0);
Grid.SetColumn(_columnMap, 0);
//clear out the content.
this.TabAdapterContent.Children.Clear();
//add new content
this.TabAdapterContent.Children.Add(_columnMap);
}
else
{
tabType = TabType.ERROR_MSG;
TextBlock tb = new TextBlock();
tb.Text = "The File: " + fileName + " is not a valid mapping file.";
Grid.SetRow(tb, 0);
Grid.SetColumn(tb, 0);
//clear out the content.
this.TabAdapterContent.Children.Clear();
//add new content
this.TabAdapterContent.Children.Add(tb);
}
}
}
The point of this is to decide what type of file is being added and load up the correct user control inside of it to display that file.
my main window xaml for the tab control is
<TabControl x:Name="tabControl" Grid.Column="1" Grid.Row="0" ItemsSource="{Binding openFileNames, Mode=OneWay}">
<TabControl.LayoutTransform>
<!-- Allows to zoom the control's content using the slider -->
<ScaleTransform CenterX="0"
CenterY="0" />
<!-- part of scale transform ScaleX="{Binding ElementName=uiScaleSlider,Path=Value}"
ScaleY="{Binding ElementName=uiScaleSlider,Path=Value}" />-->
</TabControl.LayoutTransform>
<!-- Header -->
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<!-- Content -->
<TabControl.ContentTemplate>
<DataTemplate>
<local:TabAdapter fileName="{Binding fileName}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
the header works, and if i changed
<local:TabAdapter fileName="{Binding fileName}" />
into
<TextBlock Text="{Binding fileName}" />
Then it all binds correctly, I have a feeling it has something to do with the data context on my tab adapter. but not exactly sure what it needs to be set to.
my xaml for the tab adapter is
<UserControl x:Class="ImportMappingGui.TabAdapter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="TabAdapterContent">
<Grid.RowDefinitions>
<RowDefinition Height = "*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
it will all compile and run, but only the constructor of the user control gets hit, and at that only once no matter how many tabs I create.
This is my first WPF application so my apologies if it is something stupid im missing.
(or if my methodology of setting up a adapter of sorts is not the best way of solving this issue).
Do not set the DataContext of a UserControl from within the UserControl itself. It defeats the whole point of having "lookless" controls that can be used to display whatever you pass it.
Your fileName binding is failing because the DataContext = this, and this is the TabAdapter control itself, and TabAdapter.fileName is not actually set to anything. (Remember, binding to tell a property to retrieve it's value somewhere else is different from setting the value directly)
As for the constructor not running more than once, that is by design. Since you told the TabControl to use the TabAdapter control as a template, it will create one copy of the TabAdapter, and simply replace the .DataContext behind the control whenever you switch tabs. This increases performance as it doesn't have to keep initializing and tracking a separate control for each tab, and reduces the memory used.

How do I access a TextBlock which is inside my ListBox's DataTemplate (but not Binding) in XAML?

XAML
<ListBox x:Name="lsbQueue" Margin="0,0,0,10" Grid.RowSpan="2" Loaded="lsbQueue_Loaded" SelectionChanged="lsbQueue_SelectionChanged" ItemContainerStyle="{StaticResource ListBoxItemStyle1}" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="stk" Orientation="Vertical">
<!-- This is the bugger which I need to access behind the scenes-->
<TextBlock x:Name="tbActive" FontSize="35" FontFamily="Segoe UI Symbol" Text="" Height="115" Margin="0,0,0,-110" Tag="Active"/>
<!-- -->
<TextBlock Text="{Binding Path=SongName}" FontSize="35" Width="388" FontWeight="Normal" Margin="60,0,0,0"/>
<TextBlock Width="390" FontWeight="Thin" Margin="60,-5,0,10" Opacity="0.55">
<Run Text="{Binding Artist}" />
<Run Text=", " /> <!-- space -->
<Run Text="{Binding Album}" />
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The above is my Listbox which is the populated from code behind with the help of this:
C#
void GetQueue()
{
var songs = new List<song>();
for (int i = 0; i < MediaPlayer.Queue.Count; i++)
{
songs.Add(new song {
SongName = MediaPlayer.Queue[i].Name.ToString(),
Album = MediaPlayer.Queue[i].Album.Name.ToString(),
Artist = MediaPlayer.Queue[i].Artist.ToString()
});
}
lsbQueue.ItemsSource = songs.ToList();
//lsbQueue.SelectedValue.ToString();
GlobalVars._song = MediaPlayer.Queue.ActiveSongIndex;
lsbQueue.SelectedIndex = GlobalVars._song;
// .......
}
and
public class song
{
public string SongName { get; set; }
public string Album { get; set; }
public string Artist { get; set; }
}
public class Song : List<song>
{
public Song()
{
Add(new song {
SongName = "",
Album = "",
Artist = ""
});
}
}
I have tried using VisualTreeHelper and other extension methods which can be found here:
GeekChamp
Falafel Blog
But I've had no success. I've almost given up on this. Does anyone have any ideas what can be done. Thank you.
As you can see - I can successfully get the Media Queue but I would like to show a visual hint on the left hand side of the "SelectedItem" like the playing character in the TextBlock - tbActive. Hope this helps!
Since the <TextBlock> is the first entry in the DataTemplate that you're trying to access use the provided function from the GeekChamp's tutorial.
<ListBox x:Name="lb" SelectionChanged="lb_SelectionChanged"/>
// namespaces
using System.Windows.Media;
private T FindFirstElementInVisualTree<T>(DependencyObject parentElement) where T : DependencyObject
{
var count = VisualTreeHelper.GetChildrenCount(parentElement);
if (count == 0)
return null;
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(parentElement, i);
if (child != null && child is T)
{
return (T)child;
}
else
{
var result = FindFirstElementInVisualTree<T>(child);
if (result != null)
return result;
}
}
return null;
}
private void lb_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// get the ListBoxItem by SelectedIndex OR index number
//ListBoxItem lbi = (ListBoxItem) this.lb.ItemContainerGenerator.ContainerFromIndex(lb.SelectedIndex);
// get the ListBoxItem by SelectedItem or object in your ViewModel
ListBoxItem lbi = (ListBoxItem)this.lb.ItemContainerGenerator.ContainerFromItem(lb.SelectedItem);
// get your textbox that you want
TextBlock tbActive= FindFirstElementInVisualTree<TextBlock>(lbi);
}
The above answer will throw an Exception - just like Chubosaurus Software suggested the SelectedItem will be a 'Song' and thefore the TextBlock will also be a null. And it won't work.
You can try get StackPanel from ListBox's Selected Item using as operator and then use Children property with indexer to get to your TextBlock.
StackPanel temp = lsbQueue.SelectedItem as StackPanel;
var textBlock = temp.Children[0] as TextBlock;
What exactly do you want to accomplish? Maybe another Binding + possible ValueConverter would be way better solution...

Categories

Resources