UWP - Storyboard in DataTemplate ControlStoryboardAction fires only first time - xaml

The request is that the row of my ListView blinks when the property SelectedItem of the ViewModel raises change.
This is my code, the problem is that it works only first time. Subsequent changes are ignored.
<DataTemplate x:Key="myDataTemplate">
<Grid x:Name="myGrid">
<Interactivity:Interaction.Behaviors>
<Core:DataTriggerBehavior Binding="{Binding SelectedItem}" Value="True">
<Media:ControlStoryboardAction>
<Media:ControlStoryboardAction.Storyboard>
<Storyboard>
<ColorAnimation
To="#009ABF"
Storyboard.TargetName="myGrid"
Storyboard.TargetProperty="(Grid.Background).(SolidColorBrush.Color)"
AutoReverse="True"
Duration="0:0:1"
RepeatBehavior="1x" />
</Storyboard>
</Media:ControlStoryboardAction.Storyboard>
</Media:ControlStoryboardAction>
</Core:DataTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<TextBlock Text="{Binding Name}"
Grid.Column="1"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="0,2,10,0"
FontSize="16"
TextAlignment="Left"/>
<!--OMISSIS-->
</Grid>
SelectedItem code :
public bool SelectedItem
{
get
{
return this.selectedItem;
}
set
{
this.selectedItem = value;
this.RaisePropertyChanged();
}
}

This is the solution i found.
1) Use Completed event of the Storyboard
<Storyboard Completed="SelectedItemReset" FillBehavior="Stop">
3) Use the GalaSoft.MvvmLight.Messaging.Messenger to comunicate from CodeBehind and ViewModel the reset of the property SelectedItem
Xaml
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<Grid x:Name="DataTemplateGrid">
<Interactivity:Interaction.Behaviors>
<Core:DataTriggerBehavior Binding="{Binding SelectedItem}" ComparisonCondition="Equal" Value="True">
<Media:ControlStoryboardAction ControlStoryboardOption="Play">
<Media:ControlStoryboardAction.Storyboard>
<Storyboard Completed="SelectedItemReset" FillBehavior="Stop">
<ColorAnimation
To="Lime"
Storyboard.TargetName="DataTemplateGrid"
Storyboard.TargetProperty="(Grid.Background).(SolidColorBrush.Color)"
Duration="0:0:1"/>
</Storyboard>
</Media:ControlStoryboardAction.Storyboard>
</Media:ControlStoryboardAction>
</Core:DataTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<!--OMISSIS-->
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
CodeBehind
private void SelectedItemReset(object sender, object e)
{
GalaSoft.MvvmLight.Messaging.Messenger.Default.Send<Mvvm.ViewModels.Units.SelectedItemResetMessage>(new Mvvm.ViewModels.Units.SelectedItemResetMessage());
}
MVVM Class .ctor
public MyViewModel()
{
GalaSoft.MvvmLight.Messaging.Messenger.Default.Register<SelectedItemResetMessage>(this, message =>
{
if (this.SelectedItem == true)
this.SelectedItem = false;
});
}
Note: My DataTemplate was in a separate file and linked to the ListView with the ItemTemplate property, this prevented me from calling the method Completed in CodeBehind.

Related

How to animate ListItem in ItemClick?

I am having Storyboard in my Page resources to share it between required controls. I try to animate clicked ListItem using that Storyboard in code behind by setting TargetName propery.
<Page.Resources>
<Storyboard x:Name="Story1">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="-200"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Page.Resources>
<ListView x:Name="ListView1" IsItemClickEnabled="True" ItemClick="ListView1_ItemClick">
<ListView.ItemTemplate>
<DataTemplate>
<Grid x:Name="GridData">
<Grid.RenderTransform>
<TranslateTransform x:Name="GridTrans" X="0" />
</Grid.RenderTransform>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Price}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
private void ListView1_ItemClick(object sender, ItemClickEventArgs e)
{
Story1.Stop();
var item = ListView1.ContainerFromItem(e.ClickedItem) as ListViewItem;
var grid = item.ContentTemplateRoot as Grid;
Storyboard.SetTargetName(Story1, grid.Name); //???
Story1.Begin();
}
But unable to animate clicked ListItem on ItemClick event. I get error as "Cannot resolve TargetName GridData"
The GridData is under DataTemplate, if you use SetTargetName method to set animation, it will not get correct ContentTemplateRoot. For your requirement, you could use SetTarget method.
private void ListView1_ItemClick(object sender, ItemClickEventArgs e)
{
Story1.Stop();
var item = ListView1.ContainerFromItem(e.ClickedItem) as ListViewItem;
var grid = item.ContentTemplateRoot as Grid;
Storyboard.SetTarget(Story1, grid);
Story1.Begin();
}

Showing images from the internet asynchronously

In my UWP application, I have a ListView with incremental loading. Now I need to also include an image in the ListViewItem. I tried directly giving it the URI.
where ThumbnailImage is just a string in my view model. The problem with this is that because of incremental loading, when I scroll down a little bit while the previous images are still loading, the app just hangs. Whereas if I let all the images appear and then scroll down, it works fine.
I've also tried the ImageEx control from the toolkit and it has the same problem.
I did some searching the only thing I found was doing IsAsync = True in XAML. But it seems that IsAsync is not available in UWP.
My ViewModel:
public class MyViewModel
{
...
public string ThumbnailImage { get; set; }
...
}
My XAML ListView that gets filled incrementally
<ListView Grid.Row="0"
SelectionMode="Single"
IsItemClickEnabled="True"
ItemClick="SearchResultListView_ItemClick" >
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="BorderBrush" Value="Gray"/>
<Setter Property="BorderThickness" Value="0, 0, 0, 1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ListViewItemPresenter
ContentTransitions="{TemplateBinding ContentTransitions}"
SelectionCheckMarkVisualEnabled="True"
CheckBrush="{ThemeResource SystemControlForegroundBaseMediumHighBrush}"
CheckBoxBrush="{ThemeResource SystemControlForegroundBaseMediumHighBrush}"
DragBackground="{ThemeResource ListViewItemDragBackgroundThemeBrush}"
DragForeground="{ThemeResource ListViewItemDragForegroundThemeBrush}"
FocusBorderBrush="{ThemeResource SystemControlForegroundAltHighBrush}"
FocusSecondaryBorderBrush="{ThemeResource SystemControlForegroundBaseHighBrush}"
PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}"
PointerOverBackground="{ThemeResource SystemControlDisabledTransparentBrush}"
SelectedBackground="{ThemeResource SystemControlDisabledTransparentBrush}"
SelectedForeground="{ThemeResource SystemControlDisabledTransparentBrush}"
SelectedPointerOverBackground="{ThemeResource SystemControlDisabledTransparentBrush}"
PressedBackground="{ThemeResource SystemControlDisabledTransparentBrush}"
SelectedPressedBackground="{ThemeResource SystemControlDisabledTransparentBrush}"
DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}"
DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}"
ReorderHintOffset="{ThemeResource ListViewItemReorderHintThemeOffset}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
ContentMargin="{TemplateBinding Padding}"
CheckMode="Inline"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewModel:MyViewModel">
<UserControl >
<Grid Style="{StaticResource SomeStyle}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualState x:Name="VisualStatePhone">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="ThumbnailImage.Width" Value="50"/>
<Setter Target="ThumbnailImage.Height" Value="50"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="VisualStateTablet">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="600" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="ThumbnailImage.Width" Value="70"/>
<Setter Target="ThumbnailImage.Height" Value="70"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="VisualStateDesktop">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="1200" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="ThumbnailImage.Width" Value="90"/>
<Setter Target="ThumbnailImage.Height" Value="90"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition MaxHeight="30"></RowDefinition>
<RowDefinition MaxHeight="30"></RowDefinition>
<RowDefinition MaxHeight="30"></RowDefinition>
<RowDefinition MaxHeight="30"></RowDefinition>
<RowDefinition MaxHeight="30"></RowDefinition>
<RowDefinition MaxHeight="10"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Source="{x:Bind ThumbnailImage}"
Visibility="{x:Bind ThumbnailImage, Converter={StaticResource BoolToVisibilityImage}}"
Name="ThumbnailImage"/>
<TextBlock Text="{x:Bind Name}" .../>
<Grid Grid.Row="1"
Grid.Column="1"
Visibility="{x:Bind Source, Converter={StaticResource ConverterNameHere}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind lblSource, Mode=OneWay}" .../>
<TextBlock Text="{x:Bind Source}" .../>
</Grid>
<Grid Grid.Row="2"
Grid.Column="1"
Visibility="{x:Bind Author, Converter={StaticResource ConverterNameHere}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind lblAuthor, Mode=OneWay}" .../>
<TextBlock Text="{x:Bind Author}" .../>
</Grid>
<TextBlock Text="{x:Bind EducationalLevel}" .../>
<StackPanel Orientation="Horizontal"
Grid.Row="4"
Grid.Column="1">
<ItemsControl ItemsSource="{x:Bind AccessRights}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{x:Bind Languages}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ItemsControl ItemsSource="{x:Bind TypeImages}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here's my incremental loading class:
public class ItemsToShow : ObservableCollection<MyViewModel>, ISupportIncrementalLoading
{
private SearchResponse ResponseObject { get; set; } = new SearchResponse();
MyViewModel viewModel = null;
public bool HasMoreItems
{
get
{
if ((string.IsNullOrEmpty(SearchResultDataStore.NextPageToken) && !SearchResultDataStore.IsFirstRequest) || SearchResultDataStore.StopIncrementalLoading)
return false;
if(SearchResultDataStore.IsFirstRequest)
{
using (var db = new DbContext())
{
var json = db.UpdateResponse.First(r => r.LanguageId == DataStore.Language).JsonResponse;
Metadata = Newtonsoft.Json.JsonConvert.DeserializeObject<UpdateApiResponse>(json).response.metadata.reply;
}
var returnObject = SearchResultDataStore.SearchResponse;
ResponseObject = returnObject.response;
//This will show a grid with some message for 3 seconds on a xaml page named SearchResultPage.
Toast.ShowToast(ResponseObject.message, SearchResultPage.Current.ToastGrid);
}
else
{
SearchApiResponse returnObject = null;
try
{
returnObject = new SearchApiCall().CallSearchApiAsync(param1, param2, param3).Result;
}
catch
{
return false;
}
ResponseObject = returnObject.response;
}
try
{
return ResponseObject.documents.Count > 0;
}
catch { return false; }
}
}
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
CoreDispatcher coreDispatcher = Window.Current.Dispatcher;
return Task.Run<LoadMoreItemsResult>(async () =>
{
await coreDispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
foreach (var item in ResponseObject.documents)
{
this.Add(PrepareViewModel(item));
}
});
await Task.Delay(350);
return new LoadMoreItemsResult() { Count = count };
}).AsAsyncOperation<LoadMoreItemsResult>();
}
public MyViewModel PrepareViewModel(Document document)
{
viewModel = new MyViewModel();
viewModel.property1 = document.value1;
viewModel.property2 = document.value2;
...
...
if(SearchResultDataStore.ShowImage)
{
//thumbnailUrl is a string.
viewModel.ThumbnailImage = document.thumbnailUrl;
}
else
{
viewModel.ThumbnailImage = "somegarbage.png";
}
...
...
return viewModel;
}
}
By default when you're using x:Bind for the source the binding is not updated automatically.
To verify that's actually the root cause of your problem you could set a breakpoint in your value converter, and see how many time is called.
I'd suggest using x:Bind ThumbnailImage, Mode=OneWay
You might have to implement INotifyPropertyChanged in your code behind.
Make few steps:
Add x:Phase attribute to Image control. Set biggest than other value (for ex. all are "0", set "1" etc.)
Add Mode=OneWay to Image because your should know when property will be changed.
Add control extension to Image control with dependency property ImagePath. When property will be changed -> start to load image from net asynchronically (for ex. by HttpClient). When request will be finished -> get content and put it to BitmapSource and put it to the Source property of Image.
Be sure that original image's size not much more than Image control's size, because if it's true -> UI will be hang.

Animate input box in UWP

I want to animate input box just like the one above how to do it in MVVM template 10
I have a list view
and need the search bar just like the image
I have attached an solution to your problem. There are two storyboards that are triggered on GotFocus and Lost Focus for an AutoSuggestBox in UWP c#
Here us what I achieved:
XAML :
<Page.Resources>
<Storyboard x:Name="OnCancel">
<DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetProperty="(FrameworkElement.Width)" Storyboard.TargetName="button">
<EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="70">
<EasingDoubleKeyFrame.EasingFunction>
<CircleEase EasingMode="EaseIn"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="HeaderGrid">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="51"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="HeaderGrid">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="HeaderGrid">
<EasingDoubleKeyFrame KeyTime="0" Value="-36.058"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Name="OnTextBoxFocus">
<DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetProperty="(FrameworkElement.Width)" Storyboard.TargetName="button">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.8" Value="70">
<EasingDoubleKeyFrame.EasingFunction>
<CircleEase EasingMode="EaseOut"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="HeaderGrid">
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimation Duration="0:0:0.4" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="HeaderGrid" d:IsOptimized="True"/>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="HeaderGrid">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="-36.058"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid x:Name="HeaderGrid" Margin="0,0,-1,-0.117" RenderTransformOrigin="0.5,0.5" Height="51">
<Grid.RenderTransform>
<CompositeTransform/>
</Grid.RenderTransform>
<TextBlock x:Name="textBlock" TextWrapping="Wrap" Text="TextBlock" Margin="5,0,1,3" FontSize="36" SelectionHighlightColor="{x:Null}" Foreground="DodgerBlue"/>
<TextBox Width="1" Height="1" IsReadOnly="True"/>
<Path Data="M0,48 L360,48" Height="1" Margin="0,0,0,0.117" Stretch="Fill" Stroke="DodgerBlue" UseLayoutRounding="False" VerticalAlignment="Bottom" d:LayoutOverrides="LeftPosition, RightPosition"/>
</Grid>
<StackPanel Grid.Row="1">
<Grid Height="32" Margin="12,8,12,0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<AutoSuggestBox x:Name="searchText" PlaceholderText="Search" QueryIcon="Find" TextMemberPath="name" LostFocus="searchText_LostFocus" GotFocus="searchText_GotFocus"/>
<Button x:Name="button" Content="Cancel" VerticalAlignment="Stretch" d:LayoutOverrides="Height" Grid.Column="4" Margin="5,0,0,0" Width="70" Click="button_Click"/>
</Grid>
<ListView x:Name="listView" Background="#FFECECEC" Margin="0,8,0,0">
<ListViewItem Content="List View Item 1" BorderThickness="0,0,0,1" BorderBrush="#FFB9B9B9"/>
<ListViewItem Content="List View Item 2" BorderThickness="0,0,0,1" BorderBrush="#FFB9B9B9"/>
<ListViewItem Content="List View Item 3" BorderThickness="0,0,0,1" BorderBrush="#FFB9B9B9"/>
<ListViewItem Content="List View Item 4" BorderThickness="0,0,0,1" BorderBrush="#FFB9B9B9"/>
<ListViewItem Content="List View Item 5" BorderThickness="0,0,0,1" BorderBrush="#FFB9B9B9"/>
<ListViewItem Content="List View Item 6" BorderThickness="0,0,0,1" BorderBrush="#FFB9B9B9"/>
<ListViewItem Content="List View Item 7" BorderThickness="0,0,0,1" BorderBrush="#FFB9B9B9"/>
<ListViewItem Content="List View Item 8" BorderThickness="0,0,0,1" BorderBrush="#FFB9B9B9"/>
<ListViewItem Content="List View Item 9" BorderThickness="0,0,0,1" BorderBrush="#FFB9B9B9"/>
<ListViewItem Content="List View Item 10" BorderThickness="0,0,0,1" BorderBrush="#FFB9B9B9"/>
</ListView>
</StackPanel>
</Grid>
and in code behind XAML.CS
private void searchText_LostFocus(object sender, RoutedEventArgs e)
{
OnCancel.Begin();
}
private void button_Click(object sender, RoutedEventArgs e)
{
OnCancel.Begin();
}
private void searchText_GotFocus(object sender, RoutedEventArgs e)
{
OnTextBoxFocus.Begin();
}
Also dont forget to set width of button = 0 on Initialization.
Hope this helps.
You can use data binding to bind your title and Cancel button's Visibility property to property defined in your ViewModel, as #Raunaq Patel said, the animations are triggered by GotFocus and LostFocus event.
So you can for example code like this:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock x:Name="pageHeader" Text="Main Page" Grid.Row="0" Visibility="{Binding IsVisible}" FontSize="30" />
<Grid Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<AutoSuggestBox HorizontalAlignment="Stretch" GotFocus="{x:Bind ViewModel.Search_GotFocus}"
LostFocus="{x:Bind ViewModel.Search_LostFocus}" VerticalAlignment="Stretch"
Width="{Binding BoxWidth}" />
<TextBlock Text="Cancel" Foreground="BlueViolet" Tapped="{x:Bind ViewModel.Cancel_Tapped}" Width="100"
VerticalAlignment="Stretch" HorizontalAlignment="Center" FontSize="20"
Visibility="{Binding CancelIsVisible}" Grid.Column="1" />
</Grid>
<ListView Grid.Row="1" IsEnabled="{Binding ListViewEnable}">
<ListViewItem>Item 1</ListViewItem>
<ListViewItem>Item 2</ListViewItem>
<ListViewItem>Item 3</ListViewItem>
<ListViewItem>Item 4</ListViewItem>
<ListViewItem>Item 5</ListViewItem>
</ListView>
</Grid>
</Grid>
Since you are using Template 10, code behind is for example in the MainPageViewModel like this:
public class MainPageViewModel : ViewModelBase
{
private Visibility _IsVisible;
public Visibility IsVisible
{
get { return _IsVisible; }
set
{
if (value != _IsVisible)
{
_IsVisible = value;
RaisePropertyChanged();
}
}
}
private Visibility _CancelIsVisible;
public Visibility CancelIsVisible
{
get { return _CancelIsVisible; }
set
{
if (value != _CancelIsVisible)
{
_CancelIsVisible = value;
RaisePropertyChanged();
}
}
}
private bool _ListViewEnable;
public bool ListViewEnable
{
get { return _ListViewEnable; }
set
{
if (value != _ListViewEnable)
{
_ListViewEnable = value;
RaisePropertyChanged();
}
}
}
private double _BoxWidth;
public double BoxWidth
{
get { return _BoxWidth; }
set
{
if (value != _BoxWidth)
{
_BoxWidth = value;
RaisePropertyChanged();
}
}
}
public MainPageViewModel()
{
_IsVisible = Visibility.Visible;
_CancelIsVisible = Visibility.Collapsed;
_ListViewEnable = true;
_BoxWidth = Window.Current.Bounds.Width;
}
public void Search_GotFocus(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
IsVisible = Visibility.Collapsed;
CancelIsVisible = Visibility.Visible;
ListViewEnable = false;
BoxWidth = _BoxWidth - 100;
}
public void Search_LostFocus(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
IsVisible = Visibility.Visible;
CancelIsVisible = Visibility.Collapsed;
ListViewEnable = true;
BoxWidth = Window.Current.Bounds.Width;
}
public void Cancel_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
IsVisible = Visibility.Visible;
CancelIsVisible = Visibility.Collapsed;
ListViewEnable = true;
BoxWidth = Window.Current.Bounds.Width;
}
}
Here you can see the data in ListView are fake, you can of course use DataTemplate and bind collection to the ItemSource of the ListView. Here is the rendering image of my sample:
It's AutoSuggestBox.
You can see how to use in https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/XamlAutoSuggestBox
And you can see summary on https://msdn.microsoft.com/zh-cn/windows/uwp/controls-and-patterns/auto-suggest-box?f=255&MSPPError=-2147217396

UWP CommandBar with multiple overflow menus like Word Mobile?

I have a basic C# UWP app running on the desktop. The app's layout is similar to Word Mobile, i.e. it has a main menu with a command bar below to apply various commands.
The default CommandBar has primary commands (displayed as icons) and secondary commands shown in the overflow menu.
The Word Mobile app uses a special command bar with "groups" of command buttons. Each group has their own overflow menu should the window size be too small to show all commands (see screenshots below).
Is there a way to get these "grouped" commands with their own overflow menu using standard XAML controls? If not, what would be a strategy to implement a custom control like this?
Example:
(1) Wide window: CommandBar shows all command buttons:
(2) Small window: two separate overflow menu buttons:
Sorry about the delay but I thought I would post a proof of concept answer.
For this example I have created 7 action AppBarButton in a CommandBar including the following
2 Buttons that are independent - AllAppsButton, CalculatorButton
2 Buttons that are part of the CameraGroup - CameraButton, AttachCameraButton
3 Buttons that are part of the FontGroup - BoldButton, FontButton, ItalicButton
The idea is that if the screen size is less than 501 pixels I use VisualStateManager and AdaptiveTriggers to show the Group Chevron Buttons instead of the action Buttons. The action Buttons are then shown by clicking the down chevron which opens up a relevant Popup control.
The Popups are hidden again using AdaptiveTriggers if the screen is increased to 501 pixels or above.
MainPage.xaml
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CameraGroupStates">
<VisualState x:Name="Default">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="501" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="CameraButton.Visibility" Value="Visible"/>
<Setter Target="AttachCameraButton.Visibility" Value="Visible" />
<Setter Target="CameraGroupButton.Visibility" Value="Collapsed" />
<Setter Target="CameraGroupPopup.IsOpen" Value="false" />
<Setter Target="FontButton.Visibility" Value="Visible"/>
<Setter Target="BoldButton.Visibility" Value="Visible" />
<Setter Target="ItalicButton.Visibility" Value="Visible" />
<Setter Target="FontGroupButton.Visibility" Value="Collapsed" />
<Setter Target="FontGroupPopup.IsOpen" Value="false" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Minimal">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="CameraButton.Visibility" Value="Collapsed"/>
<Setter Target="AttachCameraButton.Visibility" Value="Collapsed" />
<Setter Target="CameraGroupButton.Visibility" Value="Visible" />
<Setter Target="FontButton.Visibility" Value="Collapsed"/>
<Setter Target="BoldButton.Visibility" Value="Collapsed" />
<Setter Target="ItalicButton.Visibility" Value="Collapsed" />
<Setter Target="FontGroupButton.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<CommandBar x:Name="MainCommandBar" Height="50">
<AppBarButton x:Name="AllAppsButton" Icon="AllApps"></AppBarButton>
<AppBarButton x:Name="CameraButton" Icon="Camera"></AppBarButton>
<AppBarButton x:Name="AttachCameraButton" Icon="AttachCamera"></AppBarButton>
<!--This is the Group Chevron button for Camera-->
<AppBarButton x:Name="CameraGroupButton" Visibility="Collapsed" Content=""
Click="CameraGroupButton_Click">
<AppBarButton.ContentTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Content}"
FontFamily="Segoe MDL2 Assets" HorizontalAlignment="Center"></TextBlock>
</Grid>
</DataTemplate>
</AppBarButton.ContentTemplate>
</AppBarButton>
<AppBarButton x:Name="CaluclatorButton" Icon="Calculator"></AppBarButton>
<AppBarButton x:Name="BoldButton" Icon="Bold"></AppBarButton>
<AppBarButton x:Name="FontButton" Icon="Font"></AppBarButton>
<AppBarButton x:Name="ItalicButton" Icon="Italic"></AppBarButton>
<!--This is the Group Chevron button for Fonts-->
<AppBarButton x:Name="FontGroupButton" Visibility="Collapsed" Content=""
Click="FontGroupButton_Click">
<AppBarButton.ContentTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Content}"
FontFamily="Segoe MDL2 Assets" HorizontalAlignment="Center"></TextBlock>
</Grid>
</DataTemplate>
</AppBarButton.ContentTemplate>
</AppBarButton>
</CommandBar>
<Popup x:Name="CameraGroupPopup" HorizontalAlignment="Right" HorizontalOffset="-120"
VerticalOffset="50" IsOpen="False">
<StackPanel Orientation="Horizontal" Width="120" Height="60" Background="LightGray"
HorizontalAlignment="Center">
<AppBarButton x:Name="CameraGroupCameraButton" Icon="Camera" Width="50" Height="50"
Foreground="Black"/>
<AppBarButton x:Name="CameraGroupAttachCameraButton" Icon="AttachCamera"
Foreground="Black" Width="50" Height="50"></AppBarButton>
</StackPanel>
</Popup>
<Popup x:Name="FontGroupPopup" HorizontalAlignment="Right" HorizontalOffset="-170"
VerticalOffset="50" IsOpen="False">
<StackPanel Orientation="Horizontal" Width="170" Height="60" Background="LightGray"
HorizontalAlignment="Center">
<AppBarButton x:Name="FontGroupBoldButton" Icon="Bold" Width="50" Height="50"
Foreground="Black"/>
<AppBarButton x:Name="FontGroupFontButton" Icon="Font"
Foreground="Black" Width="50" Height="50"></AppBarButton>
<AppBarButton x:Name="FontGroupItalicButton" Icon="Italic"
Foreground="Black" Width="50" Height="50"></AppBarButton>
</StackPanel>
</Popup>
</Grid>
Code behind contains 2 fields and 2 events used to hide/show relevant Popups, change chevrons to up/down arrow.
public sealed partial class MainPage : Page
{
public bool CameraGroupIsOpen { get; set; } = false;
public bool FontGroupIsOpen { get; set; } = false;
public MainPage()
{
this.InitializeComponent();
}
private void CameraGroupButton_Click(object sender, RoutedEventArgs e)
{
if (CameraGroupIsOpen)
{
CameraGroupPopup.IsOpen = false;
CameraGroupButton.Content = "\uE019";
CameraGroupIsOpen = false;
}
else
{
CameraGroupPopup.IsOpen = true;
CameraGroupButton.Content = "\uE018";
CameraGroupIsOpen = true;
}
}
private void FontGroupButton_Click(object sender, RoutedEventArgs e)
{
if (FontGroupIsOpen)
{
FontGroupPopup.IsOpen = false;
FontGroupButton.Content = "\uE019";
FontGroupIsOpen = false;
}
else
{
FontGroupPopup.IsOpen = true;
FontGroupButton.Content = "\uE018";
FontGroupIsOpen = true;
}
}
}
Quite a bit of code that could no doubt be vastly improved as a CustomControl but wanted to show the idea. Hope it helps
It's probably not super complicated, but it can be a lot of work nevertheless. It probably derives from the the ribbon where there are multiple groups with item collapse and drop priorities (I think) and items get dropped in priority order.
You would need to have a container panel that would read priorities from the groups included in it and in MeasureOverride - it would drive collapsing items in the groups when space pressure happens.
The ToolStrip control in my toolkit has code to move items between the toolstrip and the overflow dropdown (check sample here) and could be a good first step of implementing support for ribbon-like collapsing.
IIRC the official sample for CommandBar also has logic implemented to move items between PrimaryCommands and SecondaryCommands which you could look into.

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>