Bind to bound item? - c#

I have Comments class, to which I am binding:
<ListBox ItemsSource="{Binding CommentFiles}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Text}" TextWrapping="Wrap"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding UserId}"/> <!-- Here should be username -->
<TextBlock Text=","/>
<TextBlock Text="{Binding CreatedAt}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As you can see Comments class have UserId property, which is just some char combination. I can get User class object with asynchronous getUser(userID) method.
I want to see user's username(which is in User class) instead of UserId, when I bind to comments.
And I cannot edit classes. Is there a way to do this?

You could Bind the userId in combination with a value converter that takes the userId, calls getUser(value) and returns the user name.
<TextBlock Text="{Binding UserId, Converter={StaticResource MyUserIdConverter}" />
And the value converter would look like:
public class MyUserIdConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// Add some checks here ;-)
return GetUser((string) value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

A little example based on my comment. You can either load the complete user, or only the username.
public class ExtendedCommentFile
{
private readonly CommentFile _comment;
public ExtendedComment(CommentFile comment)
{
_comment = comment;
}
public int UserId
{
get { return _comment.UserId; }
set { _comment.UserId = value; }
}
public User User
{
get { return LoadTheUserHereOrInVM(); }
}
public string Username
{
get { return LoadTheUserNameHereOrInVM(); }
}
}
/// <summary>
/// This is the unchangeable commentfile class
/// </summary>
public class CommentFile
{
public string Text { get; set; }
public int UserId { get; set; }
public DateTime CreatedAt { get; set; }
}
/// <summary>
/// This is unchangeable user class
/// </summary>
public class User
{
}

Related

How to update visibility of an UI element in a DataTemplate in GridView

I'm working on an UWP application. Which has a GridView with following structure:
<Page.Resources>
<local:boolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</Page.Resources>
<GridView ItemsSource="{x:Bind ExampleItems, Mode=OneWay}" x:Name="mDataGridView" ItemClick="mDataGridView_ItemClick" IsItemClickEnabled="True">
<GridView.ItemTemplate>
<DataTemplate x:Name="DataTemplate" x:DataType="local:ItemTemplate">
<StackPanel Height="100" Width="100" Background="OrangeRed" x:Name="rootPanel">
<TextBlock x:Name="TitleTextBlock" Text="{x:Bind Title,Mode=OneWay}"/>
<TextBlock Text="{x:Bind Subtitle,Mode=OneWay}" />
<TextBlock Text="{x:Bind Description,Mode=OneWay}" />
<ProgressBar Visibility="{x:Bind ShowProgress, Converter={StaticResource BoolToVisibilityConverter},Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
And a quite simple corresponding item data class:
public class ItemTemplate
{
public string Title { get; set; }
public string Subtitle { get; set; }
public string Description { get; set; }
public bool ShowProgress { get; set; }
}
A converter to convert the "ShowProgress" property into Visibility:
public class boolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
bool show = (bool)value;
return show ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
Visibility visibility = (Visibility)value;
return visibility.Equals(Visibility.Visible);
}
}
The code works fine to display the GridView when the application starts. But if I try to change the the progress bar visibility by changing corresponding "ShowProgress" property when the application is running, the view won't update.
ExampleItems[15].ShowProgress = true;
ExampleItems[15].Title = "New Title 15";
Any one got any idea for how to change the visibility with x:bind mechanism? Any suggestion would be appreciated. Thank you.
Alex
You need to implement the INotifyPropertyChanged interface and fire the PropertyChanged event whenever property values change.
for E.g :
public class ItemTemplate :INotifyPropertyChanged
{
private bool _showProgress;
public bool ShowProgress
{
get { return _showProgress; }
set
{
_showProgress = value;
RaisePropertyChanged("ShowProgress");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
For the complete code listing, see the XAML data binding sample.

DataBind a Dictionary value to ObservableCollection C# - XAML

I've got a Observable collection of custom objects and a public dictionary variable.
I would like the "BrandName" attribute to act as the Key for the "Brands" dictionary and bind the colour to the button. How would I go about doing this? The dictionary variable is outside of the class.
C# Code:
private ObservableCollection<BusService> BusServicesGUI;
public Dictionary<String, Brush> Brands;
public MainWindow(Dictionary<String, BusService> busServices)
{
InitializeComponent();
BusServicesGUI = new ObservableCollection<BusService>(BusServices.Values);
lstMachineFunctions.ItemsSource = BusServicesGUI;
lstMachineFunctions.Items.Refresh();
}
C# Class:
public class BusService
{
public string ServiceNumber { get; set; }
public string BrandName { get; set; }
public List<Location> Locations { get; set; }
public BusService(string brandName, string serviceNumber)
{
BrandName = brandName;
ServiceNumber = serviceNumber;
Locations = new List<Location>();
}
}
XAML CODE:
<StackPanel x:Name="ServiceStack">
<ItemsControl x:Name="lstMachineFunctions">
<ItemsControl.ItemTemplate >
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<usercontrols:BusServiceCard/>
<Button Tag="{Binding ServiceNumber}" Background="{Binding Brands[BrandName]}" Height="50" Click="ButtonCl"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
As you can see from the XAML my current attempts have been trying Background="{Binding Brands[BrandName]}" This has however not worked, any help would be much appreciated.
You can use an IValueConverter to pefrom this operation.
public class BrandColorConverter : IValueConverter
{
public Dictionary<String, Brush> Brands = new Dictionary<string, Brush>()
{
{ "brand1", Brushes.Red },
{ "brand2", Brushes.Blue }
};
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (!(value is BusService))
return Binding.DoNothing;
var busService = (BusService)value;
if (!Brands.ContainsKey(busService.BrandName))
return Binding.DoNothing;
return Brands[busService.BrandName];
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
In xaml, add it as a static resuorce:
<Window.Resources>
<local:BrandColorConverter x:Key="BrandColorConverter"/>
</Window.Resources>
And use it in your button:
<Button Tag="{Binding ServiceNumber}"
Background="{Binding Converter={StaticResource BrandColorConverter}}"
Height="50"
Click="ButtonCl"/>
This binding goes to the current element, so the whole BusService object will be passed to the converter.
Hope it solves your problem.
I would strongly advise you to look into MVVM pattern if you are going to use WPF with data binding, as it makes things much more streamlined.

Why this UWP Binding to decimal property is not working properly?

I have a decimal property called TG
public class Dados_Pessoa : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public decimal TG { get; set; }
// ...
}
public class Pessoa : INotifyPropertyChanged
{
public Pessoa Propriedades { get; set; }
// ...
}
I put on the XAML:
<TextBox Header="TG" HorizontalAlignment="Left" Margin="145,416,0,0" VerticalAlignment="Top" Width="224"
Text="{Binding Path=Pessoa.Propriedades.TG, Mode=TwoWay}"
/>
When I change the TextBox value and move to other field, this error appears in Visual Studio 2017 output:
Error: Cannot save value from target back to source.
BindingExpression: Path='Pessoa.Propriedades.TG'
DataItem='Entidades.Formularios.FormFichaCadastro'; target element is
'Windows.UI.Xaml.Controls.TextBox' (Name='null'); target property is
'Text' (type 'String').
If I change the decimal to double it works fine, as expected.
I want to use decimal to have more precision in the numbers.
Why is this behaviour and how to fix this?
I solved it by creating a Converter for these fields that I Bind to Decimal data type.
public class DecimalConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
Decimal.TryParse((string)value, out decimal result);
return result;
}
}
Then I declared it
<Page.Resources>
<local:DecimalConverter x:Key="DecimalConverter" />
</Page.Resources>
and used :
<TextBox Header="TG" HorizontalAlignment="Left" Margin="145,416,0,0" VerticalAlignment="Top" Width="224"
Text="{Binding Path=Pessoa.Propriedades.TG, Mode=TwoWay, Converter={StaticResource DecimalConverter}}"
/>

WPF UserControl based on generic object properties

I have an object based on byte data with over 200 properties that I care about, in the sense that I want to (1) know the value, and (2) know when the value changed from one message to the next.
A snippet of the XAML I am using:
<Label Content="Prop Name" />
<TextBlock Text="{Binding PropName}"
Background="{Binding PropName,
Converter={StaticResource CompareToLastValueConverter}}" />
Currently, I have these lines pasted for EACH property, with appropriate grid location settings.
My question is this: is there a nice way to create a nested WPF UserControl that takes a generic object property from the model and handles assigning the name (with spaces) to the Label, then assigning the value of the property to the TextBlock like the example above?
Also, is this the best way to think about this problem, or am I missing a link in the "WPF way" of doing things?
I've often wanted to try this. I'd create an ItemsControl template for PropertyInfo.
I created a test class:
public class MyClass
{
public string PropertyTest1 {get;set;}
public string PropertyTest2 { get; set; }
public string PropertyTest3 { get; set; }
public string PropertyTest4 { get; set; }
}
To display the properties of. In my data context for the display, I've got two things to bind to. A list of PropertyInfos, and the object in question. Since the PropertyInfo is static, you might be able to do this a better way using a converter or something, and not need to bind it to a property:
public PropertyInfo[] Properties
{
get { return typeof(MyClass).GetProperties(); }
}
public MyClass MyObject
{
get { return new MyClass { PropertyTest1 = "test", PropertyTest3 = "Some string", PropertyTest4 = "Last Property" }; }
}
Now, displaying the properties is easy:
<ItemsControl x:Name="PropertyDisplay" ItemsSource="{Binding Properties}" Grid.IsSharedSizeScope="True">
<ItemsControl.Resources>
<local:PropertyInfoValueConverter x:Key="PropertyInfoValueConverter"/>
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" Margin="4,2"/>
<TextBlock Grid.Column="1" Margin="4,2"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But these are 'static', we can't bind to any values. A way to get around that is to use the Tag property, and a multi-binding converter:
So lets add Tag="{Binding MyObject}" to our ItemsSource, and throw that and the PropertyInfo into a value converter for our second textblock:
<TextBlock Grid.Column="1" Margin="4,2">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource PropertyInfoValueConverter}">
<Binding Path=""/>
<Binding ElementName="PropertyDisplay" Path="Tag"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
The converter is actually pretty simple, especially since you're not using text-boxes (so only going the read-only direction):
public class PropertyInfoValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
PropertyInfo propertyInfo = values[0] as PropertyInfo;
return propertyInfo.GetValue(values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
This is the result:
You say you want spaces for the names, that could be done with a converter with some logic looking for whatever naming convention you've got (spaces before capital letters?).
It would be fun to play with template selectors to choose boolean, string, float templates and treat them differently. (Checkboxes, text, 00.00 formatted text etc)
Edit: Exploring Template Selector
Here's a sample template selector:
public class PropertyInfoTemplateSelector : DataTemplateSelector
{
public DataTemplate StringTemplate { get; set; }
public DataTemplate IntegerTemplate { get; set; }
public DataTemplate DecimalTemplate { get; set; }
public DataTemplate BooleanTemplate { get; set; }
public DataTemplate DefaultTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
PropertyInfo propertyInfo = item as PropertyInfo;
if (propertyInfo.PropertyType == typeof(string))
{
return StringTemplate;
}
else if (propertyInfo.PropertyType == typeof(int))
{
return IntegerTemplate;
}
else if (propertyInfo.PropertyType == typeof(float) || propertyInfo.PropertyType == typeof(double))
{
return DecimalTemplate;
}
else if (propertyInfo.PropertyType == typeof(bool))
{
return BooleanTemplate;
}
return DefaultTemplate;
}
}
Our ItemsControl is now simply:
<ItemsControl x:Name="PropertyDisplay" ItemsSource="{Binding Properties}"
Grid.IsSharedSizeScope="True"
Tag="{Binding MyObject}"
ItemTemplateSelector="{StaticResource PropertyInfoTemplateSelector}"
Margin="20"/>
I also added spaces in names using this converter:
public class PropertyInfoNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string text = value as string;
if (string.IsNullOrWhiteSpace(text))
return string.Empty;
StringBuilder newText = new StringBuilder(text.Length * 2);
newText.Append(text[0]);
for (int i = 1; i < text.Length; i++)
{
if (char.IsUpper(text[i]))
if ((text[i - 1] != ' ' && !char.IsUpper(text[i - 1])) ||
(char.IsUpper(text[i - 1]) &&
i < text.Length - 1 && !char.IsUpper(text[i + 1])))
newText.Append(' ');
newText.Append(text[i]);
}
return newText.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
(Credit to this: https://stackoverflow.com/a/272929/1305699).
Updating our class to contain some boolean and fload fields:

Bind string from .resx ResourceDictionary to TextBlock.Text using index key

i'm trying to get my view in different languages, using Properties/Resources.resx file for localization.
I've my model looks like the following:
class City
{
public int Id { get; set; }
public string LocalizationKey { get; set; }
}
The ViewModel:
class ViewModel
{
public ObservableCollection<City> Cities { get; set; }
}
And on my View, i've the following code:
<ItemsControl ItemsSource="{Binding Cities}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding LocalizationKey}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
When i want to get the value of my string key from the dictionary for only one item without Items Collection it works correctly by using the following code:
<TextBlock Text="{x:Static properties:Resources.MyStringKey}" />
The problem is when using the code above with an ItemsControl where the keys are unknowns! Is there any way to access to the dictionary values by using the LocalizationKey as an index?
Can you do something like:
public class City
{
public int Id { get; set; }
public string LocalizationKey { get; set; }
public City(string englishName)
{
LocalizationKey = Properties.Resources.ResourceManager.GetString(englishName);
}
}
I'm not sure this best practice; but it what came to mind first.
After hours of web searching, i finally found a solution by using a converter, it may not the best practices to solve the problem but at least it does exactly what i want:
My Converter:
public class LocalizationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var key = value as string;
if (!string.IsNullOrEmpty(key))
{
string dictionaryValue = Resources.ResourceManager.GetString(key);
return dictionaryValue ?? key;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and the XAML code:
<TextBlock Text="{Binding LocalizationId, Converter={StaticResource LocalizationConverter}}" />
Thanks.

Categories

Resources