I have an ItemsControl which is bound to a CollectionViewSource bound to a property on the View Model.
The ItemsControl has a GroupStyle set which looks like this:
<GroupStyle HeaderTemplate="{StaticResource TotalDurationTemplate}" />
Where TotalDurationTemplate is:
<DataTemplate x:Key="TotalDurationTemplate">
<Border BorderBrush="Black" BorderThickness="0 1" Background="#EEE">
<Grid>
<TextBlock HorizontalAlignment="Center"
FontSize="18" FontWeight="Bold"
Text="{Binding Path=Items[0].Start, Converter={StaticResource DateTimeFormatConverter}, ConverterParameter='ddd dd/MM'}" />
<TextBlock Margin="10 0" HorizontalAlignment="Right" VerticalAlignment="Center"
FontSize="16" Foreground="#9000"
Text="{Binding Items, Converter={StaticResource TotalDurationConverter}}" />
</Grid>
</Border>
</DataTemplate>
The issue is that the second TextBlock (the one bound to Items) is not re-evaluated when a new item is added to the View Model's collection (which is an ObservableCollection<>). The item is added to the ListView into the correct group but the Total Duration value is not updated.
The Converter for Total Duration looks like this:
public class TotalDurationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return
((IEnumerable<object>)value)
.Select(x => ((RecentTimingViewModel)x).Duration)
.Aggregate((v1, v2) => v1 + v2)
.TotalHours
.ToString("F2") + "h";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
How do I make the binding refresh correctly when the items in the View Model are changed?
EDIT: The Solution
I took Solution 2 from the Accepted Answer and put it into my code. This is what ended up working:
<DataTemplate x:Key="TotalDurationTemplate">
<Border BorderBrush="Black" BorderThickness="0 1" Background="#EEE">
<Grid>
<TextBlock HorizontalAlignment="Center"
FontSize="18" FontWeight="Bold"
Text="{Binding Path=Items[0].Start, Converter={StaticResource FormatDateIntelligentConverter}}" />
<TextBlock Margin="10 0" HorizontalAlignment="Right" VerticalAlignment="Center"
FontSize="16" Foreground="#9000">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TotalDurationConverter}">
<MultiBinding.Bindings>
<Binding Path="Items" />
<Binding Path="Items.Count" />
</MultiBinding.Bindings>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</Border>
</DataTemplate>
And changing TotalDurationConverter to IMultiValueConverter. The just ignore the second item in the Array.
So two possibilities and if you can try below simple solutions and let me know if it works.
Solution 1 - a very simple and basic one since you are using a textbloxk set the mode explicitly to Two way. I guess TextBlock default binding mode is One way.
Solution 2 - I have faced similar issue working with a combo box- here is a work around which worked for me
For the second Text block use Multi Binding, first bind it to List as you have already done, second bind it to any property in View Model which will be triggered when your list is getting changed (example an int property returning List.Count) - This second dummy property will make sure that your converter is re evaluated.
I guess second option should work for you.
Let me know if it doesn't work.
regards,
Vishal
Related
I'm currently using the TextBlock below to bind the value of a property named Name:
<TextBlock Text="{Binding Name}" />
Now, I want to bind another property named ID to the same TextBlock.
Is it possible to bind two or more values to the same TextBlock? Can it be done with simple concatenation, like Name + ID and, if not, how else could this be approached?
You can use a MultiBinding combined with the StringFormat property. Usage would resemble the following:
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} + {1}">
<Binding Path="Name" />
<Binding Path="ID" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Giving Name a value of Foo and ID a value of 1, your output in the TextBlock would then be Foo + 1.
Note: This is only supported in .NET 3.5 SP1 and 3.0 SP2 or later.
I know this is a way late, but I thought I'd add yet another way of doing this.
You can take advantage of the fact that the Text property can be set using "Runs", so you can set up multiple bindings using a Run for each one. This is useful if you don't have access to MultiBinding (which I didn't find when developing for Windows Phone)
<TextBlock>
<Run Text="Name = "/>
<Run Text="{Binding Name}"/>
<Run Text=", Id ="/>
<Run Text="{Binding Id}"/>
</TextBlock>
If these are just going to be textblocks (and thus one way binding), and you just want to concatenate values, just bind two textblocks and put them in a horizontal stackpanel.
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding ID}"/>
</StackPanel>
That will display the text (which is all Textblocks do) without having to do any more coding. You might put a small margin on them to make them look right though.
Use a ValueConverter
[ValueConversion(typeof(string), typeof(String))]
public class MyConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return string.Format("{0}:{1}", (string) value, (string) parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
and in the markup
<src:MyConverter x:Key="MyConverter"/>
.
.
.
<TextBlock Text="{Binding Name, Converter={StaticResource MyConverter Parameter=ID}}" />
I want to change the foreground of certain items in a ListView based on a property of such items. If the item has the property "EsBlacklist" set to true, its foreground should be red.
<Page.Resources>
<converter:ForegroundColorConverter x:Key="ForegroundConverter" x:Name="ForegroundConverter"/>
</Page.Resources>
<StackPanel Grid.Column="1" Grid.Row="1">
<TextBlock HorizontalAlignment="Center" Margin="10" FontSize="24">Vehículos sin VTV</TextBlock>
<ListView ItemsSource="{x:Bind ViewModel.PatentesSinVtv}" Margin="10" DisplayMemberPath="Placa"
SelectedItem="{x:Bind ViewModel.PatenteSeleccionada, Mode=TwoWay}"
HorizontalAlignment="Center"
IsItemClickEnabled="False"
IsSwipeEnabled="False"
CanDragItems="False"
SelectionMode="Single"
Grid.Column="1"
Grid.Row="1">
<ListViewItem Foreground="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource ForegroundConverter}}"></ListViewItem>
( Self should reference the item and not ListViewItem.)
</ListView>
</StackPanel>
And the converter:
class ForegroundColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var patente = (Patente)value; //value is not a Patente but ListViewItem
return patente.EsBlacklist ? new SolidColorBrush(Colors.Red) : new SolidColorBrush(Colors.Gray);
}
}
My problem is that "value" received in the converter is not a Patente but a ListViewItem
My problem is that "value" received in the converter is not a Patente but a ListViewItem
As the document of {RelativeSource} markup extension,
{RelativeSource Self} Produces a Mode value of Self. The target element should be used as the source for this binding. This is useful for binding one property of an element to another property on the same element.
...
The Self mode is useful for binding one property of an element to another property on the same element, and is a variation on ElementName binding but does not require naming and then self-referencing the element.
Here is an example to use the RelativeSource={RelativeSource Self},
<Rectangle
Fill="Orange" Width="200"
Height="{Binding RelativeSource={RelativeSource Self}, Path=Width}"/>
You can see the document to learn how to use {RelativeSource} markup extension
You can bind the ViewModel directly to make the converter be the Patente,
<ListViewItem Foreground="{x:Bind ViewModel, Mode=TwoWay, Converter={StaticResource ForegroundConverter}}"/>
The value of foreground color is not a plain color, but a brush.
So your converter should return new SolidColorBrush(Colors.Red).
You may deal with it like this:
<ListViewItem>
<ListViewItem.Foreground>
<SolidColorBrush Color="{x:Bind YourColor}"/>
</ListViewItem.Foreground>
</ListViewItem>
I needed to implement an ItemTemplate
<ListView.ItemTemplate>
<DataTemplate x:DataType="modelo:Patente">
<TextBlock Text="{Binding Placa}" Foreground="{x:Bind EsBlacklist, Mode=TwoWay, Converter={StaticResource ForegroundConverter}}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
and the converter becomes this:
public object Convert(object value, Type targetType, object parameter, string language)
{
return (bool)value? new SolidColorBrush(Colors.Red) : new SolidColorBrush(Colors.Gray);
}
Ok so this is kinda hard to explain. I have a view which has a tree and has a form. The tree is bound to 2 different observable collections.
<TreeView FontFamily="Calibri" FontSize="16" Name="tree" Margin="3" Grid.Row="1" Grid.Column="0" Height="950" VerticalAlignment="Top" Grid.ColumnSpan="2"
ItemsSource="{Binding Path=Org, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItemChanged="tree_SelectedItemChanged" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Org_Sredstva}">
<TextBlock Text="{Binding Path=Organizacije_Naziv}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Ossr_Naziv}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Now then next to the tree there is a form. And the form is supposed to be bound to a third observable collection that changes depending on selecteditem of tree.
<Grid Grid.Row="2" Grid.Column="2" Margin="5" DataContext="{Binding ElementName=tree, Path=SelectedItem}">
As you can see the binding is to the tree which is bound to a different observable collection.
<extToolkit:WatermarkTextBox x:Name="RadnoVrijeme" Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="2"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Width="100"
Height="25"
Padding="3"
Margin="3"
AcceptsReturn="True"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
Validation.Error="Validation_Error">
<extToolkit:WatermarkTextBox.Watermark>
<StackPanel Orientation="Horizontal">
<TextBox Text="Radno Vrijeme" Margin="4,2,2,0" FontWeight="Regular" Foreground="Silver" BorderThickness="0"/>
</StackPanel>
</extToolkit:WatermarkTextBox.Watermark>
<extToolkit:WatermarkTextBox.Text>
<Binding Path="Opr_RadnoVrijeme" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
<Binding.ValidationRules>
<validation:InputLenghtRule MinLength="10" MaxLength="250" ValidatesOnTargetUpdated="True"/>
</Binding.ValidationRules>
</Binding>
</extToolkit:WatermarkTextBox.Text>
</extToolkit:WatermarkTextBox>
This is a textbox and it is bound to a property on the third collection but it doesn't work obviously because that collection is never set as itemssource anywhere.
The workaround I found is to in codebehind set every textbox individually so
this.View.RadnoVrijeme.Text = opr.Opr_RadnoVrijeme;
Where the left side of the equation is the text property of the textbox and the right side is the collection.property. This works but I don't like it and was hoping there was a way to actually bind this?
You should definetely have a look at MVVM. It utilize modular design, where your View is consisting of multiple Views controlled by ViewModels. Then it's easy to solve any kind of dependency (relation? selection?) problem.
In your case you are trying to bind to SelectedItem directly, while in MVVM you will handle selection and then you have possibility to rise notification of related property change, which will cause update of corresponding View element automatically.
Here is a generic example:
ItemVM _selectedItem;
public ItemVM SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged();
// trigger update in the view
OnPropertyChanged(nameof(SomeRelatedProperty));
}
}
// bind TextBlock.Text to that property
public string SomeRelatedProperty
{
get
{
// some logic based on selected item
if(SelectedItem.SomeProperty == SomeValue)
return "a";
...
return "b";
}
}
The cooler thing if SelectedItem already provides value as one of property (because it's a ViewModel), then assuming all ViewModels implement INotifyPropertyChanged you can simply bind to that property directly and whenever selected item is changed the view will be updated to display that property too:
<TextBlock Text="{Binding SelectedItem.SomeProperty} ... / >
As for error you are getting:
System.Windows.Data Error: 40 : BindingExpression path error: 'Opr_RadnoVrijeme' property not found on 'object' ''Organizacije' (HashCode=23451234)'. BindingExpression:Path=Opr_RadnoVrijeme; DataItem='Organizacije' (HashCode=23451234); target element is 'WatermarkTextBox' (Name='RadnoVrijeme'); target property is 'Text' (type 'String')
Try binding (pretty unsure, you didn't provide ViewModels to figure that out):
<WatermarkTextBox Text="{Binding SelectedItem.Opr_RadnoVrijeme, RelativeSource={RelativeSource FindAncestor, AncestorType=TreeView}}" ... />
I'm developing a Windows Phone 8.1 App. In the app I'm parsing a Json data. From the parsed data, I'm populating a GridView. The JsonData consists of temperature in Kelvin. But I want to change the temperature in Celsius while binding the data in the TextBlock.
Here's the Code in XAML
<StackPanel>
<TextBlock x:Name="tblk1" FontSize="20" />
<TextBlock x:Name="tblk2" FontSize="20" Margin="0,5,0,0"/>
<TextBlock x:Name="tblk3" FontSize="20" Margin="0,5,0,0"/>
<ScrollViewer VerticalScrollBarVisibility="Visible" VerticalScrollMode="Enabled" >
<GridView x:Name="tempList" ItemsSource="{Binding}">
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10" Background="DarkGreen">
<StackPanel Margin="5">
<TextBlock Text="{Binding dt}" FontSize="15" />
<TextBlock Text="{Binding temp.day}" FontSize="15" />
<TextBlock Text="{Binding temp.min}" FontSize="15" />
<TextBlock Text="{Binding temp.max}" FontSize="15" />
</StackPanel>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</ScrollViewer>
</StackPanel>
And here's code from the C#
protected override void OnNavigatedTo(NavigationEventArgs e)
{
RootObject obj = JsonConvert.DeserializeObject<RootObject>(MainPage.jsonReturn);
tblk1.Text = obj.city.name;
tblk2.Text = obj.cod;
tblk3.Text = obj.message.ToString();
tempList.DataContext = null;
tempList.DataContext = obj.list;
}
In the output, Here the min and max temp is shown in Kelvin. How do I show it in Celsius?
PS There's no any way to change the Incoming JsonData in Celsius. It
has to changed in runtime.
To answer your specific question, it sounds like you want to use a converter with the binding. E.g.:
class KelvinToCelsiusConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (double)value - 273.15;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (double)value + 273.15;
}
}
(Strictly speaking the ConvertBack() method could be left unimplemented, i.e. just throw new NotImplementedException();, but with the above it would also allow for e.g. editable TextBox bindings).
You could then use the above like this, for example:
<TextBlock Text="{Binding Path=temp.day, Converter={StaticResource kelvinToCelsiusConverter1}}" FontSize="15" />
…where you have of course declared the static resource in question, e.g.:
<GridView.Resources>
<local:KelvinToCelsiusConverter x:Key="kelvinToCelsiusConverter1" />
</GridView.Resources>
(I assume here, of course, that you have declared the KelvinToCelsiusConverter class in the namespace which is declared in your XAML as local. Lacking a good, minimal, complete code example, it's impossible to know for sure what all of the above should look like, including namespace declarations).
I also concur with the other observations made by commenter IronSlug:
You should be using binding for all of the displayed data, not just the templated items in your GridView control (i.e. tblk1, tblk2, and tblk3, and any other controls similarly set explicitly in code-behind).
Setting anything to null just before you assign it some different non-null value is pointless.
I've created a listbox, to which i can add and delete items dynamically UI changes accordingly and it works fine.
<ListBox Name="MsgsList" ItemsSource="{Binding Items}" Style="{StaticResource MsgsBoxStyle}">
<ListBox.ItemTemplate>
<DataTemplate x:Name="MsgsDataTemplate">
<StackPanel Tag="{Binding MsgTagInfo}" ManipulationCompleted="StackPanel_Msgs_ManipulationCompleted">
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener Hold="GestureListener_Hold" Tap="GestureListener_Tap"/>
</toolkit:GestureService.GestureListener>
<Grid x:Name="ContentPanelInner" Grid.Row="1" Width="500">
<StackPanel x:Name="stackPanelInner" Width="500">
<Grid VerticalAlignment="Top" Width="500">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding MsgTitle}" Style="{StaticResource MsgLine1}" />
<TextBlock Grid.Column="1" Text="{Binding MsgDate}" Style="{StaticResource MsgDate}" />
</Grid>
<TextBlock Text="{Binding MsgBody}" Style="{StaticResource MsgLine2}" />
</StackPanel>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
but i didn't understand how to change a style for a particular item's textblock, say based on some condition, if i want to change color of specific item's textbox(s), not sure how to access that.
can somebody please help me with this? thanks.
Probably not the easiest way to do this, but arguably the cleanest from a separation of concerns point of view is by using a converter, and binding that to the property you want to monitor...
For example, if your model is changing state based on a boolean property called myProperty, you could use something like this.
<StackPanel Background={Binding myProperty, Converter={StaticResource myBindingConverter}" />
Your converter should return a SolidColorBrush, based on the value of your property.
public class AlternateRowColour : IValueConverter
{
SolidColorBrush normal = new SolidColorBrush(Colors.Transparent);
SolidColorBrush highlighted = new SolidColorBrush(Color.FromArgb(255, 241, 241, 241));
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var myValue = (bool)value
return myValue ? highlighted : normal ;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
I If you just want to change on aspect of an items style, for example its color, you could expose that as property of the model object you are binding. For example, add a property TextColor and bind it as follows:
<TextBlock Text="{Binding MsgBody}" Style="{StaticResource MsgLine2}">
<TextBlock.Color>
<SolidColorBrush Color="{Binding TextColor}"/>
</TextBlock.Color>
</TextBlock>
This will take precedence over the colour defined via the style.