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.
Related
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>
I'm trying to build a settings page to allow the user to choice which action to execute on item swipe, like the Outlook app.
To do this I created an enum containing the available actions, and I'm binding it to a ComboBox.
Everything works, the user can choose the action and his choice is saved correctly. The problem is that the ComboBox doesn't show the selected item when I navigate to the page, it shows it only after selection.
This means that if user changes selection then the ComboBox is updated, but the selected item is shown as blank upon navigation.
Here's my code:
(XAML)
<ComboBox x:Uid="LeftActionComboBox"
Grid.Row="0"
HorizontalAlignment="Stretch"
SelectedItem="{Binding LeftSwipeActionType, Mode=TwoWay, Converter={StaticResource StringToSwipeActionTypesConverter}}"
ItemsSource="{Binding LeftSwipeActionType, Converter={StaticResource EnumToStringListConverter}}"/>
(VM Property)
public SwipeActionTypes LeftSwipeActionType
{
get { return _settings.LeftSwipeActionTypeProperty; }
set
{
_settings.LeftSwipeActionTypeProperty = value;
// RaisePropertyChanged causes a StackOverflow, but not using it is not the problem since the ComboBox is empty only before set
}
}
(Converter StringToSwipeActionTypesConverter, localization-ready)
// Returns localized string value for the Enum
public object Convert(object value, Type targetType, object parameter, string language)
{
var enumValue = (SwipeActionTypes) value;
switch (enumValue)
{
case SwipeActionTypes.Copy:
return App.ResourceLoader.GetString("CopySwipeActionName");
case SwipeActionTypes.Delete:
return App.ResourceLoader.GetString("DeleteSwipeActionName");
default:
throw new ArgumentOutOfRangeException();
}
}
// Parses the localized string into the enum value
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
var stringValue = (string) value;
if (stringValue.Equals(App.ResourceLoader.GetString("CopySwipeActionName")))
{
return SwipeActionTypes.Copy;
}
if (stringValue.Equals(App.ResourceLoader.GetString("DeleteSwipeActionName")))
{
return SwipeActionTypes.Delete;
}
return null;
}
(Converter EnumToStringListConverter)
public object Convert(object value, Type targetType, object parameter, string language)
{
var valueType = value.GetType();
return Enum.GetNames(valueType).ToList();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
Any idea on why this is failing?
The reason you are getting a StackOverflow exception is because every time you change LeftSwipeActionType property you are changing the ItemsSource of the ComboBox which changes the SelectedItem which fires INotifyPropertyChanged which changes the ItemsSource and so on and so on.
Once you stop using the same property for ItemsSource and SelectedItem then the correct initial selection will be set.
Rather than use a converter to create your ItemsSource you should just create is in your ViewModel
public MyViewModel(type enumType)
{
SourceForItems = Enum.GetValues(enumType);
}
public IEnumerable SourceForItems { get; private set; }
First of all, here is whats wrong with your approach:
You are binding your ItemsSource to the same property as the SelectedItem, even tough you are using a converter this can cause an infinite update circle - and you don't want that.
Generating the same static list of elements over and over again seems a bit wasteful. Instead of passing an instance of a type, lets just pass the type itself to the converter:
EnumToMembersConverter.cs
public class EnumToMembersConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return Enum.GetValues((Type)value).ToList();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return DependencyProperty.UnsetValue;
}
}
XAML
ItemsSource="{Binding Source={x:Type whateverNamespace:SwipeActionTypes}, Converter={StaticResource EnumToMembersConverter}}"
This will give you all Values of SwipeActionTypes, therefore you can bind it directly, without converting back again.
SelectedItem="{Binding LeftSwipeActionType, Mode=TwoWay}"
There is nothing wrong with using a ComboBox for types other than string, so lets make this your base for further steps:
<ComboBox x:Uid="LeftActionComboBox"
Grid.Row="0"
HorizontalAlignment="Stretch"
SelectedItem="{Binding LeftSwipeActionType, Mode=TwoWay}"
ItemsSource="{Binding Source={x:Type whateverNamespace:SwipeActionTypes}, Converter={StaticResource EnumToMembersConverter}}"/>
The reason you wrote all those converts is probably because the ComboBox showed strange values instead of readable strings. No worries, we already have your converter, you just need to invert it (Convert SwipeActionTypes to String) and apply it to a TextBox:
<ComboBox x:Uid="LeftActionComboBox"
Grid.Row="0"
HorizontalAlignment="Stretch"
SelectedItem="{Binding LeftSwipeActionType, Mode=TwoWay}"
ItemsSource="{Binding Source={x:Type whateverNamespace:SwipeActionTypes}, Converter={StaticResource EnumToMembersConverter}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=., Converter = {StaticResource SwipeActionTypesStringConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Note, I didn't run this code so you might need to adjust the used namespaces accordingly
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.
I just made my first converter to convert from int to string. I have a combobox fill with integers(years) but if the value is 0 I want the combobox to show 'All'.
This is my converter:
public class IntToString : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
int intY = (int)value;
if (intY == 0)
{
String strY = "All";
return strY;
}
else
{
return intY.ToString();
}
}
return String.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
}
}
In XAML where should I set the converter ? I tried in the ItemsSource of the combobox:
ItemsSource="{Binding YearsCollection, Converter={StaticResource intToStringYearConverter}}"
But I always get InvalidcastException on this line:
int intY = (int)value;
The problem is that you are trying to convert the entire collection rather than just one item from the collection.
You would want to do something like this:
<ListBox ItemsSource="{Binding YearsCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border DataContext="{Binding Converter={StaticResource intToStringYearConverter}">
...
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You can't use the converter like this, converter in ItemsSource is supposed to convert whole collection, not individual items. The collection object can't be cast to integer, so you get the exception.
You have to use DataTemplate and apply the converter on individual items.
Or - if all you need is cast to int - you could use ItemStringFormat.
Also, for setting the default message when the source is null, you can use TargetNullValue property of a Binding.
Q: Why does my custom TextBox UserControl using a MultiBinding and IMultiValueConverter gets its Convert() method called only once (during instanciation) ??
I have defined a UserControl that requires a MultiBinding and a IMultiValueConverter in order to change its behavior/presentation upon 2 indenpendant DependencyProperty.
<proj:MyControl Value="10" Digits="1" />
UserControl:
<UserControl x:Class="MyControl"
x:Name="uc"
...>
<UserControl.Resources>
<conv:DecimalToStringMultiConverter x:Key="DecToString" />
</UserControl.Resources>
[...]
<Grid>
<ctrl:VTextBox x:Name="vTb" Grid.Column="0" Margin="0,0,2,0">
<ctrl:VTextBox.Text>
<MultiBinding Converter="{StaticResource DecToString}" UpdateSourceTrigger="LostFocus" Mode="TwoWay">
<Binding ElementName="uc" Path="Value" Mode="TwoWay" />
<Binding ElementName="uc" Path="Digits" Mode="TwoWay" />
</MultiBinding>
</ctrl:VTextBox.Text>
</ctrl:VTextBox>
</Grid>
</UserControl>
When executing the application, the UserControls are all correctly instanciated. However, the IMultiValueConverter.Convert() method gets called only once.
Using an simple Binding + IValueConverter with a constant ConvertParameter worked great: the converter's Convert() method would get called everytime the TextBox contained inside the UserControl had its Text property changed.
Design changed and I had to resort to using a MultiBinding + IMultiValueConverter, and now the Convert() method only gets called once, and the TextBox.Text property is never updated upon the LostFocus() event.
What gives?
The MultiValueConverter is defined as below. I just wrap the IMultiValueConverter upon the IValueConverter to reuse existing code.
[ValueConversion(/*sourceType*/ typeof(Decimal), /*targetType*/ typeof(string))]
public class DecimalToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return "0.00";
int? digits = parameter as int?;
if(digits == null)
digits = 2;
NumberFormatInfo nfi = (NumberFormatInfo) CultureInfo.InvariantCulture.NumberFormat.Clone();
nfi.NumberGroupSeparator = " ";
nfi.CurrencyDecimalSeparator = ".";
nfi.NumberDecimalDigits = (int)digits;
return ((decimal)value).ToString("n", nfi);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return 0.00m;
decimal d;
return decimal.TryParse((string)value, out d) ? d : 0.00m;
}
}
[ValueConversion(/*sourceType*/ typeof(Decimal), /*targetType*/ typeof(string))]
public class DecimalToStringMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
DecimalToStringConverter conv = new DecimalToStringConverter();
return conv.Convert(values[0], targetType, values.Length > 1 ? values[1] : null, culture);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
DecimalToStringConverter conv = new DecimalToStringConverter();
return new[] { conv.ConvertBack(value, targetTypes[0], null, culture) };
}
}
It seems like you have some conflicting expectations about the updating behavior of the Binding and TextBox. The only reason Convert will be called multiple times is if the values of Digits or Value change multiple times, and there is nothing in your posted code to indicate that will happen. Changes to TextBox.Text won't cause calls to Convert, but should instead be calling ConvertBack on every change+LostFocus. Are you seeing that when you run your code?
You also need to return two values, instead of the one there now, from your ConvertBack method in order to supply both of the Bindings used in the MultiBinding with values.