Changing visibility of listview item - xaml

I had another question about binding visibility to a listview item and that got answered.
The issue I have now is I am trying to bind visibility to a property of the listview item but it doesn't seem to work.
Here is my code:
<ListView
ItemsSource="{Binding FooList}"
ItemTemplate="{StaticResource FooTemplate}"
Grid.Row="3">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Padding" Value="0" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
and the template:
<DataTemplate x:Key="FooTemplate">
<Grid Background="White" HorizontalAlignment="Stretch">
//template stuff
</Grid>
If I try to set the visibility on the Grid in the template it binds correctly and doesn't display Foo items that it shouldn't, but the space for the container is still there, it's just blank.
If I try to set the visibility binding in the ItemContainer:
<Setter Property="Visibility" Value="{Binding FooVisibility}" />
Then the property FooVisibility never gets called.
What's the appropriate way to hide specific ListView items given that I have an appropriate property that returns a Visibility enum?

UWP does not support bindings in Style Setters: https://learn.microsoft.com/en-us/uwp/api/Windows.UI.Xaml.Setter#Windows_UI_Xaml_Setter_Value
You could bind the Visibility property of the root element in the ItemTemplate:
<DataTemplate x:Key="FooTemplate">
<Grid Background="White" HorizontalAlignment="Stretch"
Visibility="{Binding FooVisibility}">
</Grid>
</DataTemplate>
Or you could write some code that sets up the binding programmatically in an event handler for the DataContextChanged event for the root element as suggested here: https://blog.magnusmontin.net/2016/02/28/disabling-selection-of-some-items-in-a-uwp-listview/
private void Grid_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
Grid grid = sender as Grid;
ListViewItem lvi = listView.ContainerFromItem(grid.DataContext) as ListViewItem;
if (lvi != null)
{
lvi.SetBinding(ListViewItem.VisibilityProperty, new Binding() { Path = new PropertyPath("FooVisibility"), Source = grid.DataContext });
}
}
Alternatively, you could create your own binding helper class:
UWP Binding in Style Setter not working
Universal Apps: How to bind a property of a ListViewItem (container) to the actual item (View Model)?

Related

How to do relative binding for button flyout in universal windows store app

I want to bind my button flyout width and height with some other control but it not working
<Grid >
<Popup IsOpen="True" Name="popup">
<Grid Name="popupBorder" Background="Red" Height="100" Width="100" >
</Grid>
</Popup>
<Button Content="check flyout" Name="btn" Click="Button_Click" >
<Button.Flyout>
<Flyout>
<Border Name="flyoutBorder" Height="{Binding Path=Height, ElementName=popupBorder, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
Width="{Binding Path=Width, ElementName=popupBorder, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}">
</Border>
</Flyout>
</Button.Flyout>
</Button>
</Grid>
I have also given datacontext to button but still same
DataContext="{Binding ElementName=localContext, Mode=OneWay}"
When you bind data in the content of Flyout, the binding source is in Page, and binding target is in PopupRoot, they have different DataContext, so can't it work here.
Besides, a Flyout control is like this:
If you place a Border as Content of Flyout, it will be placed in the ScrollContentPresenter:
As you can see, if you set the Width and Height of content, it will not effect the size of the Flyout since there is a ScrollViewer inside it: ScrollViewer's content has unlimited height and width.
I want to bind my button flyout width and height with some other control but it not working
So, the right way to custom the size of Flyout is to style the FlyoutPresenter for example like this:
<Flyout>
<Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter">
<Setter Property="MinHeight" Value="100" />
<Setter Property="MinWidth" Value="100" />
</Style>
</Flyout.FlyoutPresenterStyle>
<Border Name="flyoutBorder" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
</Border>
</Flyout>
Here we need to use MinHeight and MinWidth to set the size of Flyout, but since you want to bind Width and Height to some other control, and the Windows Runtime doesn't support a Binding usage for Setter.Value (the Binding won't evaluate and the Setter has no effect, you won't get errors, but you won't get the desired result either).
Usually when need data binding in Setter.Value, we can create some attached dependency properties. And to solve the different DataContext problem, we need to define the property in code behind or view model for example like this:
public static double NewWidth { get; set; } = 100.0;
Then bind the this property to the Width of popupBorder:
<Grid>
<Popup IsOpen="False" Name="popup">
<Grid Name="popupBorder" Background="Red" Height="100" Width="{Binding NewWidth}">
</Grid>
</Popup>
<Button Content="check flyout" Name="btn" Click="Button_Click" Foreground="Black">
<Button.Flyout>
<Flyout>
<Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter">
<Setter Property="local:BindingBuilder.FlyoutWidth" Value="400" /> <!--This value has no meaning here, you can set it to any value.-->
<Setter Property="MinHeight" Value="100" />
</Style>
</Flyout.FlyoutPresenterStyle>
<Border Name="flyoutBorder" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
</Border>
</Flyout>
</Button.Flyout>
</Button>
</Grid>
To register attached property, you can for example code like this:
public class BindingBuilder : DependencyObject
{
public static readonly DependencyProperty FlyoutWidthProperty =
DependencyProperty.RegisterAttached("FlyoutWidth", typeof(double), typeof(BindingBuilder), new PropertyMetadata(0, OnFlyoutWidthChanged));
public static double GetFlyoutWidth(DependencyObject obj)
{
return (double)obj.GetValue(FlyoutWidthProperty);
}
public static void SetFlyoutWidth(DependencyObject obj, double value)
{
obj.SetValue(FlyoutWidthProperty, value);
}
public static void OnFlyoutWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
double newFlyoutWidth = (double)d.GetValue(FlyoutWidthProperty);
var presenter = (FlyoutPresenter)d;
presenter.MinWidth = MainPageViewModel.NewWidth;
}
}
Here I only attached the MinWidth property, you can also use the same method to custom the MinHeight of FlyoutPresenter.

ListBox and ApplicationBar: last element padding

I have a ListBox and ApplicationBar with Opacity=.5. I want that ListBox items are under the ApplicationBar.
Like figure:
But when I scrolled to end of list then last element of ListBox is under the ApplicationBar.
I want that last element is over ApplicationBar.
Can I add padding for a last element of the ListBox (LongListSelector)? How can solved this issue?
UPDATE
<ListBox Name="MyListBox">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<i:Interaction.Triggers>
<ec:DataTrigger Binding="{Binding RelativeSource=
{RelativeSource Self},
Converter={StaticResource IsLastItemInContainerConverter}}"
Value="True">
<ec:ChangePropertyAction PropertyName="Padding" Value="0,0,0,72"/>
</ec:DataTrigger>
</i:Interaction.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Background="Red"
Width="400"
Height="120"
Margin="15">
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
UPDATE 2
My problem can be easily solved by using LongListSelector. Just add empty ListFooterTemplate with Height = ApplicationBar height.
You can use converter to check if it's last item in listbox and in case yes, set padding to desired value via DataTrigger.
Check my answer over here for converter code. Just for sake of completeness of this answer i will post that converter code here:
public class IsLastItemInContainerConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
DependencyObject item = (DependencyObject)value;
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
return ic.ItemContainerGenerator.IndexFromContainer(item)
== ic.Items.Count - 1;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and use it like this:
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource=
{RelativeSource Self},
Converter={StaticResource IsLastItemInContainerConverter}}"
Value="True">
<Setter Property="Padding" Value="5"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
edit: Didn't see the update to the original question. The below remains for additional detail.
This solution is for Windows Phone 8 only (although you could use the LongListSelector from the Windows Phone Toolkit if you are targeting 7.1/5).
I'd replace the ListBox with the LongListSelector from the SDK
<Grid>
<controls:LongListSelector ItemTemplate="{StaticResource Item}" ListFooterTemplate="{StaticResource Footer}" />
</Grid>
and for the templates add
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="Item">
<StackPanel Background="Red"
Width="400"
Height="120"
Margin="15">
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="Footer">
<Border Height="72" />
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
Maybe simplest solution is to customize ItemsPanel property on ListBox:
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Margin="0,0,0,150"/> <!-- set margin to show some 'blank' space after last item -->
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
...
</ListBox>
But be aware that by default ListBox uses VirtualizedStackPanel as ItemsPanel. If you change it to StackPanel, it may bring some performance penalty. This is solution is suitable for lists with few items.

Change the Foreground color of a TextBlock inside a ListView's DataTemplate when the item is selected

I'm building a Windows Store app with C#/XAML.
I have a simple ListView bound to an ItemsSource. There's a DataTemplate which defines the structure of each item and that has a ContentControl and a TextBlock in it.
I wish to change the Foreground colour of the TextBlock when the item is selected. Does anyone know how I can do this?
<ListView Grid.Column="1"
ItemsSource="{Binding Categories}"
ItemContainerStyle="{StaticResource CategoryListViewItemStyle}"
Background="{StaticResource DeepRedBrush}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ContentControl Content="{Binding Id, Converter={StaticResource Cat2Icon}}" HorizontalAlignment="Left" VerticalAlignment="Center" Width="110" Foreground="#FF29BCD6"/>
<TextBlock x:Name="catName" HorizontalAlignment="Left" Margin="0" TextWrapping="Wrap" Text="{Binding Name}" Grid.Column="1" VerticalAlignment="Center" FontSize="18.667"
Foreground="White"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
At the moment it's set to "White", so all I need is some binding expression that will change the Foreground property depending on the selected state of the item in the listview.
This does what you are asking for.
Using this XAML
<Grid x:Name="LayoutRoot" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="MyListView" ItemsSource="{Binding Items}" SelectionMode="Single" SelectedItem="{Binding Selected, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Height="100" Width="300">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Ellipse x:Name="ellipse">
<Ellipse.Fill>
<SolidColorBrush Color="{Binding Color}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="10" Text="{Binding Title}" Style="{StaticResource HeaderTextBlockStyle}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
And this code behind:
public class MyModel : BindableBase
{
string _Title = default(string);
public string Title { get { return _Title; } set { SetProperty(ref _Title, value); } }
Color _Color = Colors.White;
public Color Color { get { return _Color; } set { SetProperty(ref _Color, value); } }
}
public class MyViewModel : BindableBase
{
public MyViewModel()
{
var items = Enumerable.Range(1, 10)
.Select(x => new MyModel { Title = "Title " + x.ToString() });
foreach (var item in items)
this.Items.Add(item);
}
MyModel _Selected = default(MyModel);
public MyModel Selected
{
get { return _Selected; }
set
{
if (this.Selected != null)
this.Selected.Color = Colors.White;
SetProperty(ref _Selected, value);
value.Color = Colors.Red;
}
}
ObservableCollection<MyModel> _Items = new ObservableCollection<MyModel>();
public ObservableCollection<MyModel> Items { get { return _Items; } }
}
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T storage, T value, [System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (!object.Equals(storage, value))
{
storage = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
protected void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
It will update your data template for you.
I want to make this quick point: updating the content of your list through the ViewModel is the easiest and most light-weight approach. In this case, I am updating the color which is bound to the ellipse. However, if this were a complex set of changes, I might just set a style instead. Another option is to hide and show an entire set of controls in the template. You cannot, however, change the data template because it will not be re-rendered until the grid re-draws, and that's not what you want to do.
Just like changing the Ellipse color, you could change the TextBlock Foreground like you asked in your question. Either way, this gets you what you want in the most elegant way.
Best of luck!
You can simply handle the SelectionChanged event on the ListView and change the Foreground of the previously selected item and the newly selected item by either changing a view model value on SelectedItem that is bound to your Foreground.
You can also find the TextBlock using ListView.ItemContainerGenerator.ContainerFromItem(ListView.SelectedItem) + VisualTreeHelper as in Jerry Nixon's blog post and changing the Foreground directly, though that technique has a problem if your ListView is virtualized (which it is by default), since if you scroll away from the selected item and back - the item view with the changed Foreground might be recycled and used for another item in your collection.
Another option is to bind the Foreground to the IsSelected property of the parent ListViewItem which you can do in many ways as well. You could for example put your entire DataTemplate in a UserControl and bind the Foreground to the Parent of that control. The problem is I think Parent is not a dependency property and I see no ParentChanged event on FrameworkElement (base class for UserControl that defines the Parent property), so it might be tough to go this route. Another way to bind these is to define an attached dependency property or behavior that would set up that binding for you, but that is complicated (though I have already created one you could use here).
Finally you could modify your ListView.ItemContainerStyle and change the SelectedBackground value. If that works - it would be the ideal solution.
<Style TargetType="ListViewItem">
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}"/>
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="TabNavigation" Value="Local"/>
<Setter Property="IsHoldingEnabled" Value="True"/>
<Setter Property="Margin" Value="0,0,18,2"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ListViewItemPresenter CheckHintBrush="{ThemeResource ListViewItemCheckHintThemeBrush}" CheckBrush="{ThemeResource ListViewItemCheckThemeBrush}" ContentMargin="4" ContentTransitions="{TemplateBinding ContentTransitions}" CheckSelectingBrush="{ThemeResource ListViewItemCheckSelectingThemeBrush}" DragForeground="{ThemeResource ListViewItemDragForegroundThemeBrush}" DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}" DragBackground="{ThemeResource ListViewItemDragBackgroundThemeBrush}" DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}" FocusBorderBrush="{ThemeResource ListViewItemFocusBorderThemeBrush}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Padding="{TemplateBinding Padding}" PointerOverBackgroundMargin="1" PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}" PointerOverBackground="{ThemeResource ListViewItemPointerOverBackgroundThemeBrush}" ReorderHintOffset="{ThemeResource ListViewItemReorderHintThemeOffset}" SelectedPointerOverBorderBrush="{ThemeResource ListViewItemSelectedPointerOverBorderThemeBrush}" SelectionCheckMarkVisualEnabled="True" SelectedForeground="{ThemeResource ListViewItemSelectedForegroundThemeBrush}" SelectedPointerOverBackground="{ThemeResource ListViewItemSelectedPointerOverBackgroundThemeBrush}" SelectedBorderThickness="{ThemeResource ListViewItemCompactSelectedBorderThemeThickness}" SelectedBackground="{ThemeResource ListViewItemSelectedBackgroundThemeBrush}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Silverlight 4. How to set a ToolTip with controls in a ResourceDictionary file with Style definitions?

I've looked at tens of Q&As, but haven't found the answer to this apparently simple need.
I'm working with Silverlight 4. I want to define a ToolTip WITH CONTROLS IN IT at the ResourceDictionary file that has the Style definitions.
My user control file "UC_Activity.xaml" has:
...
<TextBox Style="{StaticResource Style0}" Name="tb_id" />
...
If my "Styles.xaml" file has
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
<Style x:Key="Style0" TargetType="TextBox">
<Setter Property="FontSize" Value="12" />
<Setter Property="FontFamily" Value="Portable User Interface" />
<Setter Property="ToolTipService.ToolTip" Value="Long tooltip text here. This WORKS, but part of the text ends up out of the screen." />
</Style>
</ResourceDictionary>
it works, but I can only show simple text as the ToolTip, and if the text is very long, it will end up out of the screen, where it is impossible to be seen. What I want is something like this:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
<Style x:Key="Style0" TargetType="TextBox">
<Setter Property="FontSize" Value="12" />
<Setter Property="FontFamily" Value="Portable User Interface" />
<Setter Property="ToolTipService.ToolTip">
<Setter.Value>
<StackPanel>
<sdk:Label Content="Short text here."/>
<TextBlock TextWrapping="Wrap" MaxWidth="200" Text="Long text here. This does NOT WORK." />
</StackPanel>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
but it does NOT WORK. It builds ok, but it gives an exception ("Value does not fall within the expected range.") when starting execution.
Please, how can I do it?
Thank you very much.
I had a similar issue, the problem is that it needs to be a DataTemplate so a different instance is added to the visual tree every time its used. I created an attached property for this:
then it can just be used as follows:
<Setter Property="ControlsBehaviours:TooltipTemplate.Template">
<Setter.Value>
<DataTemplate>
<ToolTip Content="tooltip" />
</DataTemplate>
</Setter.Value>
</Setter>
public class TooltipTemplate
{
/// <summary>
/// Template Dependency Property.
/// </summary>
public static readonly DependencyProperty TemplateProperty =
DependencyProperty.RegisterAttached(
"Template",
typeof (DataTemplate),
typeof (TooltipTemplate),
new PropertyMetadata(new PropertyChangedCallback(TemplateChanged)));
public static void SetTemplate(DependencyObject o, DataTemplate value)
{
o.SetValue(TemplateProperty, value);
}
public static DataTemplate GetTemplate(DependencyObject o)
{
return (DataTemplate) o.GetValue(TemplateProperty);
}
private static void TemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ToolTipService.SetToolTip(d, ((DataTemplate)e.NewValue).LoadContent());
}
}

Silverlight TreeViewItem, Binding to IsSelected, how to?

In reading a post about TreeView and binding to a view model (http://www.codeproject.com/KB/WPF/TreeViewWithViewModel.aspx), it seems that binding a TreeViewItem IsSelected property is possible. However, I have the following code which always fails on Initialize() because it's trying to set a read-only property?
<sdk:TreeView Grid.Column="0" Grid.Row="2" Style="{StaticResource TreeViewStyle}"
ItemsSource="{Binding tvData}" >
<sdk:TreeView.ItemContainerStyle>
<Style TargetType="sdk:TreeViewItem">
<Setter Property="IsEnabled" Value="True" />
<Setter Property="IsExpanded" Value="True" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</sdk:TreeView.ItemContainerStyle>
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ItemName}" FontWeight="{Binding ItemFontWeight}"/>
</StackPanel>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
You can't assign a binding via a Setter in a Style. Effectively what you are doing there is attempting to set a binding on the Setter.Value property. Xaml doesn't infer that you mean to set a binding on the target property. In turn the Setter just assumes you are trying to set a value directly to IsSelected which it knows is read only hence the error.
I can recommend this technique for getting around the problem:
http://blogs.msdn.com/b/delay/archive/2009/05/07/one-more-platform-difference-more-or-less-tamed-settervaluebindinghelper-makes-silverlight-setters-better.aspx
EDIT:
I should mention that I have not tried the technique for this exact scenario (binding the IsSelected property of a TreeViewItem), but I have used it on numerous other occasions, and so far it has worked flawlessly.