WPF custom textblock layout - c#

I'm new to WPF, and I'm wondering if it's possible to create a custom TextBlock so that a string can be rendered in a certain way.
So for any given string:
Wrap the string at a maximum of 32 characters (or any arbitrary length)
Insert a space between the fourth and fifth character
Insert a tab between the sixth and seventh character
Add a vertical pipe at the end of the line, before wrapping.
The list goes on, essentially I'm wondering if I create a custom TextBlock inheriting from TextBlock, can I override the default behaviour in some way?

You could write a ValueConverter that transforms the contents of TextBlock.Text:
public class MyTextConverter : IValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (string.IsNullOrEmpty(values?[0]))
{
return string.Empty;
}
var manipulatedString = values[0] as string;
// Do something with the string...
return manipulatedString;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then use the converter in your Window or UserControl:
MyNamespace.MyWindowClass.xaml.cs
public class MyWindowClass : Window
{
public MyWindowClass() {}
public MyTextProperty { get; set; }
}
MyNamespace.MyWindowClass.xaml
<Window
x:Class="MyNamespace.MyWindowClass"
xmlns:local="clr-namespace:MyNamespace"
DataContext="{Binding RelativeSource={RelativeSource Self}}"/>
<Window.Resources>
<local:MyTextConverter x:Key="myTextConverterInstance"/>
</Window.Resources>
<!-- ... -->
<TextBlock Text="{Binding MyTextProperty,
Converter={StaticResource myTextConverterInstance}}"/>
<!-- ... -->
</Window>

Related

Get line count in textblock- windows phone 8

I have a textblock in a Data template in which I am displaying data through binding. Initially I need to show data upto three lines in textblock. For more data there is See more option tapping which expands the textblock.
Upto this things are done. The main problem I am facing is if the data size is not more than three lines, I don't have to shoe see more option.
How do I get to know that my data consumes just 1 or 2 lines of textblock
Thanks in advance
Consider you have view model bound to your template
public class ViewModel : INotifyPropertyChanged
{
public string MyBoundText { get { .. }; set { .. }; }
]
You can just create another property:
public int LinesNo => this.MyBoundText.Split('\n').Length;
//remember about null-check and other edge-cases
I assume you have in your xaml somewhere:
<TextBox Text="{Binding MyBoundText}"></TextBox>
SO, create own converter:
class Button1VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targettype, object parameter, System.Globalization.CultureInfo culture)
{
int mode = (int)value;
if (mode <= (int)parameter)
return System.Windows.Visibility.Collapsed;
else
return System.Windows.Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
Then bind it with your button:
<Button Content="Show more...">
<Button.Visibillity>
<Binding Path="LinesNo "
Converter="{StaticResource Button1VisibilityConverter }">
<Binding.ConverterParameter>
<sys:Int32>3</sys:Int32>
</Binding.ConverterParameter>
</Binding>
</Button.Visibillity>
</Button>
(Remember to place converter as static resoruce before).
Warning: not tested, it is only idea / hint.

Is there an IValueConverter that does if-then

What I am trying to do:
<Grid>
<Grid.RowDefinitions>
...
<!--The next line is pseudo code for what I am trying to achieve-->
<RowDefintion Height="if(EditEnabled) { 10* } else { 0 }" />
...
</Grid.RowDefinition>
...
<DockPanel Visibility="{Binding EditEnabled, Converter={StaticResource InverseBooleanToVisibilityConverter}}" ...>
...
I am trying to change the visibility of the DockPanel depending on whether editing is enabled, while keeping he ability to resize and have fixed heights and relative heights.
The question:
Is there an IValueConverter (System.Windows.Data.IValueConverter) that can take a boolean, and two numbers and choose one of the GridLengths based on the boolean? From just inspecting the interface of IValueConverter it doesn't look like this is quite the right type to use.
Or is there a better way to inject the GridLength that I want?
What I have tried:
Looking through the inheritors of IValueConverter - nothing obvious to me
Moving the Height="10*" inside the DockPanel tag and changing the RowDefinition to be Auto - this created an conversion exception
Searching here
Unfortunately, there is no IValueConverter that does if-then.
(and to be more specific: you can not do if-then logic with the XAML)
But you can do the if-then logic in the C# code.
here is the solution
public class HeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool enableEdit = (bool)value;
double param = System.Convert.ToDouble(parameter);
if (enableEdit)
return new GridLength(param, GridUnitType.Star);
else
return new GridLength(0);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
and the window like this.
<Window.Resources>
<local:HeightConverter x:Key="heightConverter"/>
<sys:Int32 x:Key="param">10</sys:Int32>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding Path=EditEnabled, Converter={StaticResource heightConverter}, ConverterParameter={StaticResource param}}" />
</Grid.RowDefinitions>
</Grid>
please consider also define the required namespace that you will use, like the following
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:[your namespace]"
Update the same result could be achieved by using IMutliValueConverter
public class HeightMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool enableEdit = (bool)values[0];
double param = System.Convert.ToDouble(values[1]);
if (enableEdit)
return new GridLength(param, GridUnitType.Star);
else
return new GridLength(0);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
and the window like this
<Window.Resources>
<local:HeightMultiConverter x:Key="heightMutliConverter"/>
<sys:Int32 x:Key="param">10</sys:Int32>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition >
<RowDefinition.Height>
<MultiBinding Converter="{StaticResource heightMutliConverter}">
<Binding Path="EditEnabled"/>
<Binding Source="{StaticResource param}"/>
</MultiBinding>
</RowDefinition.Height>
</RowDefinition>
</Grid.RowDefinitions>
</Grid>
NOTE: just do not forget, you have to take care of the Source by setting the DataContext property.
There is a built-in converter you may be able to use: AlternationConverter. You specify a list of values (of arbitrary type), bind to an integer, and the converter looks up the integer in the list of values (modulo the value count).
If you specify two values for this AlternationConverter, and you're able to provide your EditEnabled property as an integer 0 or 1, then you can map that 0 and 1 to any value you want.
If you feel it doesn't make sense to convert your bool to an integer first (something I can sympathise with), you could still use AlternationConverter as inspiration for a custom converter that doesn't require the model value to be of type int.
Create a BooleanConverter<T> base class as described in http://stackoverflow.com/a/5182660/469708
public class BooleanConverter<T> : IValueConverter
{
public BooleanConverter(T trueValue, T falseValue)
{
True = trueValue;
False = falseValue;
}
public T True { get; set; }
public T False { get; set; }
public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is bool && ((bool) value) ? True : False;
}
public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is T && EqualityComparer<T>.Default.Equals((T) value, True);
}
}
Then write
public class BooleanToGridLengthConverter : BooleanConverter<System.Windows.GridLength>
{
public BooleanToGridLengthConverter() : base(
new System.Windows.GridLength(1, System.Windows.GridUnitType.Star),
new System.Windows.GridLength(0))
{
}
}
The values for true and false can be set directly, no need for a MultiValueConverter (as long as only the boolean parameter needs to be bindable).
<convert:BooleanToGridLengthConverter x:Key="Converter" True="10*" False="0" />
Alternatively, you can derive the converter from MarkupExtension and directly use it like this:
<RowDefinition Height="{Binding EditEnabled, Converter={convert:BooleanToGridLengthConverter True=10*, False=0}" />

Different Iitems in GridView

I'm trying to implement the following "start screen" interface for my Windows Store App.
I've figured a Gridview would be the component to use.
How do i display different type of items in a GridView?
Is this a good approach:
<GridView.ItemTemplate>
<DataTemplate>
<Grid>
<ContentControl Content="{Binding Converter={StaticResource local:ContentTypeToControlConverter}}" />
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
And Class
public class ContentTypeToControlConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value != null)
{
if (value is MenuItem)
{
return new MenuItemControl();
}
else if (value is RecentViewItem)
{
return new RecentItemControl();
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
If you are targeting Windows 8.1 or higher - you could use the Hub control. That way you can avoid having to specify groups of items for your GridView, but implementing a DataTemplateSelector and setting it as a ItemTemplateSelector property of the GridView is the way to have items based on different templates.

How to data-bind to a property of a composite object in a collection?

I need to allow selection of several items from this predefined list:
public enum QuarkType{
Up,
Down,
[Description("Magical Being")] Charm,
[Description("Quite Odd")] Strange,
Top,
Bottom
}
So I use CheckComboBox, and use the DescriptionAttribute where I need to use custom description. I feed the CheckComboBox using a MarkupExtension that returns a list of all values of the given enum as IEnumerable<EnumDescriptionPair>, where EnumDescriptionPair is:
public class EnumDescriptionPair{
public object Value { get; set; }
public string Description { get; set; }
}
Now the problem is how to pass the Values of this list to the code-behind list:
public ObservableCollection<QuarkType> SelectedQuarksList { get; set; }
I mean, how to take just the Value out of the EnumDescriptionPair for each item of the selected list ?
This is what I have thus far. It obviously doesn't work (meaning it shows the right strings in the CheckComboBox, and allows selecting several items, but isn't reflected in the SelectedQuarksList mentioned above):
<Window x:Class="MyEditor.MainWindow"
xmlns:loc="clr-namespace:MyEditor"
xmlns:toolKit="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<toolKit:CheckComboBox x:Name="Ccb" Delimiter=","
ItemsSource="{loc:EnumItemsValueConverter {x:Type loc:QuarkType}}"
DisplayMemberPath="Description"
SelectedItemsOverride="{Binding SelectedQuarksList}" />
<ListBox ItemsSource="{Binding SelectedQuarksList}" />
</StackPanel>
</Window>
To do exactly what your question asks, you could try using a converter on the SelectedQuarksList binding that does a ".Select(q => q.Value)" in the ConvertBack function.
To get the behavior you want, I have done this successfully in the past (example with 2 of your values), this sets up the enum as "Flags" so the value sequence goes 0, 1, 2, 4...:
<StackPanel Orientation="Horizontal">
<Checkbox Content="Up" IsChecked="{Binding Path=SelectedQuarksFlags, Converter={Static Resource HasFlagToBoolConverter}, ConverterParamater={x:Static Quarks.Up}}"
<Checkbox Content="Magical Being" IsChecked="{Binding Path=SelectedQuarksFlags, Converter={Static Resource HasFlagToBoolConverter}, ConverterParamater={x:Static Quarks.Charm}}"
</StackPanel>
The converter looks like:
Quark _lastSeenValue;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Quark paramQuark = (Quark)parameter;
Quark currentQuark = (Quark)value;
_lastSeenValue = currentQuark;
return currentQuark.HasFlag(paramQuark);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Quark newQuark = _lastSeenValue;
Quark paramQuark = (Quark)parameter;
if ((bool)value)
{
newQuark |= paramQuark;
}
else
{
newQuark &= ~paramQuark;
}
_lastSeenValue = newQuark;
return newQuark;
}
This could be converted to add or remove from a list relatively easily, but I know the code above works.

How to pass a static value to IValueConverter in XAML

I would like to use static texts fetched from a web service in my WP7 app. Each text has a Name (the indetifier) and a Content property.
For example a text could look like this:
Name = "M43";
Content = "This is the text to be shown";
I would then like to pass the Name (i.e. the identifier) of the text to an IValueConverter, which would then look up the the Name and return the text.
I figured the converter to look something like this:
public class StaticTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
return App.StaticTexts.Items.SingleOrDefault(t => t.Name.Equals(value)).Content;
}
return null;
}
}
Then in the XAML:
<phone:PhoneApplicationPage.Resources>
<Helpers:StaticTextConverter x:Name="StaticTextConverter" />
</phone:PhoneApplicationPage.Resources>
...
<TextBlock Text="{Binding 'M43', Converter={StaticResource StaticTextConverter}}"/>
However, this does not seem to work and I am not sure that I pass in the value to the converter correctly.
Does anyone have some suggestions?
I finally found the answer. The answer was a mix between that of #Shawn Kendrot and another question I asked here: IValueConverter not getting invoked in some scenarios
To summarize the solution for using the IValueConverter I have to bind my control in the following manor:
<phone:PhoneApplicationPage.Resources>
<Helpers:StaticTextConverter x:Name="TextConverter" />
</phone:PhoneApplicationPage.Resources>
<TextBlock Text="{Binding Converter={StaticResource TextConverter}, ConverterParameter=M62}" />
Since the ID of the text is passed in with the converter parameter, the converter looks almost the same:
public class StaticTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter != null && parameter is string)
{
return App.StaticTexts.Items.SingleOrDefault(t => t.Name.Equals(parameter)).Content;
}
return null;
}
}
However, as it turns out, the binding and thus the converter is not invoked if it does not have a DataContext. To solve this, the DataContext property of the control just has to be set to something arbitrary:
<TextBlock DataContext="arbitrary"
Text="{Binding Converter={StaticResource TextConverter}, ConverterParameter=M62}" />
And then everything works as intended!
The problem lies in your binding. It will check the DataContext, and on this object, it will try to evaluate the properties M62 and ValueboxConsent on that object.
You might want to add static keys somewhere in your application where you can bind to:
<TextBlock Text="{Binding Source="{x:Static M62.ValueboxConsent}", Converter={StaticResource StaticTextConverter}}" />
Where M62 is a static class where your keys are located.. like so:
public static class M62
{
public static string ValueboxConsent
{
get { return "myValueBoxConsentKey"; }
}
}
If you want to use a value converter, you'll need to pass the string to the parameter of value converter
Xaml:
<TextBlock Text="{Binding Converter={StaticResource StaticTextConverter}, ConverterParameter=M43}"/>
Converter:
public class StaticTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter != null)
{
return App.StaticTexts.Items.SingleOrDefault(t => t.Name.Equals(parameter)).Content;
}
return null;
}
}
xmlns:prop="clr-namespace:MyProj.Properties;assembly=namespace:MyProj"
<TextBlock Text="{Binding Source={x:Static prop:Resources.MyString}, Converter={StaticResource StringToUpperCaseConverter}}" />

Categories

Resources