Showing images from the internet asynchronously - xaml

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.

Related

Access and modify child items of Button in UWP C++/CX app at runtime

I'm pretty new to UWP Xaml C++/CX development so I apologize if I use the wrong terms.
I'm trying to make an image/photo thumbnail selection tray. The user is to select which folder to browse and the contents are displayed in the scrollable thumbnail tray with the filename displayed below.
Pages are built in xaml and i'm using C++/CX. The thumbnail tray is built using the following xaml
<ScrollViewer Grid.Row="1" Margin="20,20,20,20" ScrollViewer.VerticalScrollBarVisibility="Hidden">
<Grid x:Name="thumb_grid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
</Grid>
</ScrollViewer>
I dynamically add new rows via code using
AddRow() {
RowDefinition^ rd = ref new RowDefinition();
rd->Height.Auto;
thumb_grid->RowDefinitions->Append(rd);
}
Then I populate each grid element with a new Button using
AddImageButton(int row, int col) {
Button^ b = ref new Button();
auto style = R->Lookup("ButtonStyle1");
b->Style = safe_cast<Windows::UI::Xaml::Style^>(style);
thumb_grid->Children->Append(b);
thumb_grid->SetRow(b, row);
thumb_grid->SetColumn(b, col);
}
Where "ButtonStyle1" is defined in my dictionary.xaml as
<Style x:Key="ButtonStyle1" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}"/>
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundTransparentBrush}"/>
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}"/>
<Setter Property="Padding" Value="8,4,8,4"/>
<Setter Property="Margin" Value="10"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}"/>
<Setter Property="UseSystemFocusVisuals" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<!--REMOVED THIS SECTION TO REDUCE EXAMPLE CODE-->
</VisualStateManager.VisualStateGroups>
<ContentPresenter x:Name="ContentPresenter" AutomationProperties.AccessibilityView="Raw"
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}"
Content="{TemplateBinding Content}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
<Image x:Name="thumb_image" Grid.Row="0" HorizontalAlignment="Stretch" Source="Assets/demo.jpg"/>
<TextBlock x:Name="thumb_filename" Grid.Row="1" Text="demo.jpg" FontSize="20" Foreground="White"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
TextAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
All this results in creating the desired output as shown in the following image. Obviously I'm just using a hardcoded image and text for the Button content at the moment.
The Problem
The question is how do I dynamically change the content of the custom image buttons?
Currently I can change the images within a button by accessing the content by name. However this requires the image content of a button have a unique name.
Xaml code
<Button x:Name="btnToggleShowHide" Grid.Row="1" Height="50" Width="50"
HorizontalAlignment="Left" VerticalAlignment="Bottom"
Background="Transparent"
Click="btnToggleShowHide_Click">
<Image x:Name="imgToggleShowHideBtn"
Source="Assets/Graphics/show.png"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
</Button>
and the C++/CX code for changing the image (excluding the actually logic, just showing the code used)
show = ref new BitmapImage(ref new Uri(imgToggleShowHideBtn->BaseUri->RawUri, "Assets/Graphics/show.png"));
hide = ref new BitmapImage(ref new Uri(imgToggleShowHideBtn->BaseUri->RawUri, "Assets/Graphics/hide.png"));
imgToggleShowHideBtn->Source = show;
imgToggleShowHideBtn->Source = hide;
But I cannot directly access the text within the custom button image "demo.jpg" by using its name
thumb_filename->Text = "test.jpg";
because the textblock thumb_filename only exists at runtime. In AddImageButton() I need to somehow set the content but can't figure out how to access the child objects in the button. In my mind I want to do something like
Button^ b = ref new Button();
auto style = R->Lookup("ButtonStyle1");
b->Style = safe_cast<Windows::UI::Xaml::Style^>(style);
//NOT ACTUAL CODE - THIS IS WHAT I'M TRYIN TO ACHIEVE
b->Image->Source = img; //obviously wont work because it has no knowledge whether ButtonStyle1 contains an image
b->TextBlock->Text = filename; //obviously wont work because it has no knowledge whether ButtonStyle1 contains a textblock
So I ended up using the approach suggested by Nico Zhu - MSFT and using GridView with data binding.
Firstly I created a button class (Note: I'm still in the process of refining so there may be some redundant snippets)
namespace Models {
[Windows::UI::Xaml::Data::Bindable]
public ref class FileBrowser sealed
{
private:
Platform::String^ _fname;
Platform::String^ _path;
ImageSource^ _thumbImage;
BitmapImage^ _imagedata;
public:
FileBrowser();
property Platform::String^ Filename
{
Platform::String^ get()
{
return _fname;
}
void set(Platform::String^ value)
{
_fname = value;
}
}
property Platform::String^ Filepath
{
Platform::String^ get()
{
return _path;
}
void set(Platform::String^ value)
{
_path = value;
}
}
property ImageSource^ ThumbImage
{
ImageSource^ get()
{
return _thumbImage;
}
void set(ImageSource^ value)
{
_thumbImage = value;
}
}
property BitmapImage^ ImageData
{
BitmapImage^ get()
{
return _imagedata;
}
void set(BitmapImage^ value)
{
_imagedata = value;
}
}
};
}
Add #include "FileBrowser.h" to the "MainPage.xaml.h". In my View.xaml page that contains the image browser/list add xmlns:local="using:Models" to the tag.
My GridView is created in the following way
<GridView x:Name="thumb_grid" Grid.Row="1" Margin="20,20,20,20" IsItemClickEnabled="True" SelectionMode="Single" ItemClick="thumb_grid_ItemClick">
<GridView.ItemTemplate>
<DataTemplate>
<Grid MaxHeight="200" MinHeight="200" MinWidth="200" MaxWidth="200">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Source="{Binding ImageData}"/>
<TextBlock Text="{Binding Filename}" Grid.Row="1" FontSize="20" Foreground="Gray" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
<GridView.Items>
</GridView.Items>
</GridView>
At runtime a new button is added to the GridView using (where f and I supply the relevant file and image data)
auto btn = ref new Models::FileBrowser();
btn->Filename = f->GetAt(i)->DisplayName + f->GetAt(i)->FileType;
btn->Filepath = f->GetAt(i)->Path;
btn->ImageData = I;
thumb_grid->Items->Append(btn);
This now results in the desired image tray that displays the contents of a folder. Each image clickable/touchable and calls the same function, in this case each item in the GridView calls thumb_grid_ItemClick() when pressed.
Data from the button can be obtained using the following
void View::thumb_grid_ItemClick(Platform::Object^ sender, Windows::UI::Xaml::Controls::ItemClickEventArgs^ e)
{
auto btn = (Models::FileBrowser^)e->ClickedItem;
}

Animating Xaml Grid.ColumnDefinitions using XAML Behaviors when Binding Changes

I have a grid showing data received from a web service as below:
The graph bars are achieved using data binding, with the converter returning a GridLength Star value:
<Grid Grid.Row="1" BorderThickness="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="{Binding home.possessionPercentage, Converter={StaticResource statswidthConverter}}"/>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="{Binding away.possessionPercentage, Converter={StaticResource statswidthConverter}}"/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="tbl1" HorizontalAlignment="Center" TextWrapping="Wrap" Text="{Binding home.possessionPercentage}"/>
<Rectangle Fill="#FF1DEE00" Stroke="White" Grid.Column="1" StrokeThickness="0"/>
<Rectangle Fill="White" Stroke="White" Grid.Column="2" StrokeThickness="0"/>
<Rectangle Fill="#FF139D00" Stroke="White" Grid.Column="3" StrokeThickness="0"/>
<TextBlock x:Name="tbr1" HorizontalAlignment="Center" TextWrapping="Wrap" Text="{Binding away.possessionPercentage}" Grid.Column="4"/>
</Grid>
What I would like to achieve is for the column sizes to be animated to the new value when the binding value is updated rather than just a jump to the new size. I believe this can be achieved with the Microsoft.Behaviors library - https://github.com/Microsoft/XamlBehaviors/ but am unsure where to start. Any advice please?
To meet your requirements, firstly you need an animation to animate the Width property. I wrote a simple demo which use DoubleAnimation to animate the Width of the Rectangle. For the reason I set the target of storyboard to Rectangle is that ColumnDefinition.Width property is GridLength type that we cannot use DoubleAnimation.
Secondly, we need a trigger to trigger the animation. Here I use DataTriggerBehavior in XamlBehaviors SDK. Once the data greater than one value the trigger can be triggered.Completed demo as follows.
<Page
...
xmlns:Interactions="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:Media="using:Microsoft.Xaml.Interactions.Media">
<Page.Resources>
<local:statswidthConverter x:Name="statswidthConverter" />
<Storyboard x:Name="Increase">
<DoubleAnimation
Duration="0:0:5"
EnableDependentAnimation="true"
Storyboard.TargetName="rec1"
Storyboard.TargetProperty="Width"
To="{Binding homePercentage, Converter={StaticResource statswidthConverter}}" />
</Storyboard>
<Storyboard x:Name="decrease">
<DoubleAnimation
Duration="0:0:5"
EnableDependentAnimation="true"
Storyboard.TargetName="rec2"
Storyboard.TargetProperty="Width"
To="{Binding awayPercentage, Converter={StaticResource statswidthConverter}}" />
</Storyboard>
</Page.Resources>
<StackPanel
Padding="50"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
Loaded="Grid_Loaded">
<Grid
Grid.Row="1"
Height="50"
BorderThickness="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition x:Name="clo1" Width="Auto" />
<ColumnDefinition Width="3" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<TextBlock
x:Name="tbl1"
HorizontalAlignment="Center"
Text="{Binding homePercentage}"
TextWrapping="Wrap" />
<Rectangle
x:Name="rec1"
Grid.Column="1"
Width="0"
Fill="#FF1DEE00"
Stroke="White"
StrokeThickness="0">
<Interactivity:Interaction.Behaviors>
<Interactions:DataTriggerBehavior
Binding="{Binding homePercentage}"
ComparisonCondition="GreaterThan"
Value="0">
<Media:ControlStoryboardAction Storyboard="{StaticResource Increase}" />
</Interactions:DataTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Rectangle>
<Rectangle
Grid.Column="2"
Fill="White"
Stroke="White"
StrokeThickness="0" />
<Rectangle
x:Name="rec2"
Grid.Column="3"
Width="200"
Fill="#FF139D00"
Stroke="White"
StrokeThickness="0" >
<Interactivity:Interaction.Behaviors>
<Interactions:DataTriggerBehavior
Binding="{Binding awayPercentage}"
ComparisonCondition="LessThan"
Value="200">
<Media:ControlStoryboardAction Storyboard="{StaticResource decrease}" />
</Interactions:DataTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Rectangle>
<TextBlock
x:Name="tbr1"
Grid.Column="4"
HorizontalAlignment="Center"
Text="{Binding awayPercentage}"
TextWrapping="Wrap" />
</Grid>
</StackPanel>
</Page>
Code behind
public sealed partial class MainPage : Page
{
ObservableCollection<Percentage> percentages;
public MainPage()
{
this.InitializeComponent();
percentages = new ObservableCollection<Percentage>()
{
new Percentage {homePercentage=63,awayPercentage=37 }
};
this.DataContext = percentages[0];
}
}
public class Percentage
{
public double homePercentage { get; set; }
public double awayPercentage { get; set; }
}
public class statswidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return (double)value * 2;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return (double)value / 2;
}
}
If you still want to set animation for ColumnDefinition.Width please use ObjectAnimationUsingKeyFrames which don't have so smoothly effects as DoubleAnimation the demo showed. To be smoothly need quite a lot frames. For example:
<Storyboard x:Name="storyobejct">
<ObjectAnimationUsingKeyFrames
Duration="0:0:3"
Storyboard.TargetName="clo1"
Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0" Value="1" />
<DiscreteObjectKeyFrame KeyTime="0:0:1" Value="50" />
<DiscreteObjectKeyFrame KeyTime="0:0:1.5" Value="60" />
<DiscreteObjectKeyFrame KeyTime="0:0:2.0" Value="100" />
<DiscreteObjectKeyFrame KeyTime="0:0:3" Value="126" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
Using which animation and which trigger depend on your layout and requirements. Pay attention that animating the Width seems to be a dependent animation which is not recommended. More details animation please reference Animations overview, more details about XAML behavior please reference this document.

XamlParseException on Initialize component WP8

As the title says I get a XamlParseException when I try to run the app. I have only just started learning xaml and am currently following a tutorial from a book called Windows Phone 8 game development here is the exception it is giving me.
System.Windows.Markup.XamlParseException occurred
HResult=-2146233087
Message=The property 'System.Windows.Controls.Panel.Children' is set more than once. [Line: 53 Position: 74]
Source=System.Windows
LineNumber=53
LinePosition=74
StackTrace:
at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)
at SpaceAim3D.Views.MenuPage.InitializeComponent()
at SpaceAim3D.Views.MenuPage..ctor()
InnerException:
here is my code
public partial class MenuPage : PhoneApplicationPage
{
private Dictionary<String, String> m_urls = new Dictionary<string, string>();
public MenuPage()
{
InitializeComponent();
m_urls["play"] = "/Views/GamePage.xaml";
m_urls["ranks"] = "/Views/RanksPage.xaml";
m_urls["map"] = "/Views/MapPage.xaml";
m_urls["world"] = "/Views/WorldPage.xaml";
m_urls["help"] = "/Views/HelpPage.xaml";
m_urls["web"] = "/Views/WebPage.xaml";
m_urls["settings"] = "/Views/SettingsPage.xaml";
}
//This is a common event handler used by all entities on menu page
private void BrdPage_Tap(object sender, GestureEventArgs e)
{
//To get the tag name we case the sender object and get the tag as a string
String page = ((Border)sender).Tag as String;
NavigationService.Navigate(new Uri(m_urls[page], UriKind.Relative));
}
}
and here is my Xaml code
<phone:PhoneApplicationPage
x:Class="SpaceAim3D.Views.MenuPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Landscape" Orientation="Landscape"
mc:Ignorable="d"
shell:SystemTray.IsVisible="True">
<phone:PhoneApplicationPage.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="125" />
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="/Assets/asteroid.png" />
</Setter.Value>
</Setter>
</Style>
<Style TargetType="TextBlock" x:Key="ButtonText">
<Setter Property="FontSize" Value="40"/>
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
</phone:PhoneApplicationPage.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0"
Style="{StaticResource PhoneTextTitle1Style}">
Space Aim <Bold>3D</Bold>
</TextBlock>
<Grid.Background>
<ImageBrush ImageSource="/Assets/background.png" />
</Grid.Background>
<Border Grid.Row="1" Margin="10,5,536,194" Tap="BrdPage_Tap" Tag="play">
<TextBlock Style="{StaticResource ButtonText}" Text="PLAY!"/>
</Border>
<Border Margin="255,74,291,221" Grid.RowSpan="2" Tap="BrdPage_Tap" Tag="ranks">
<TextBlock Style="{StaticResource ButtonText}" Text="RANKS!" Margin="12,66,10,66" Width="160"/>
</Border>
<Border Grid.Row="1" Margin="12,162,534,37" Tap="BrdPage_Tap" Tag="map">
<TextBlock Style="{StaticResource ButtonText}" Text="MAP"/>
</Border>
<Border Grid.Row="1" Margin="238,189,308,10" Tap="BrdPage_Tap" Tag="world">
<TextBlock Style="{StaticResource ButtonText}" Text="WORLD"/>
</Border>
<Border Grid.Row="1" Margin="420,76,126,123" Tap="BrdPage_Tap" Tag="web">
<TextBlock Style="{StaticResource ButtonText}" Text="WEB"/>
</Border>
<Border Grid.Row="1" Margin="556,148,-10,51" Tap="BrdPage_Tap" Tag="help">
<TextBlock Style="{StaticResource ButtonText}" Text="HELP"/>
</Border>
<Border Margin="503,54,12,241" Grid.RowSpan="2" Tap="BrdPage_Tap" Tag="settings">
<TextBlock Style="{StaticResource ButtonText}" Text="SETTINGS"/>
</Border>
</Grid>
</phone:PhoneApplicationPage>
I moved all the xaml code with border tags above the grid.background and it worked

GridView with 2 columns, fill width

The result I want to achieve is pretty simple, a list with 2 columns, both with equal width. In Windows Phone 7/8 this could easily be achieved using a ListBox with a WrapPanel as ItemsPanel and setting the ItemWidth to 240 (as the screen width was 480).
Now I'm Writing a Universal App, but here the problem is that the screen is not guaranted to have a width of 480 (not even for the Phone it seems) so I can't set the ItemWidth as I want it to fill the width of the screen. I have been able to achieve almost the desired effect using the following XAML:
<GridView ItemsSource="{Binding Results}" Margin="12">
<GridView.ItemTemplate>
<DataTemplate>
<Grid>
<Image Source="{Binding SampleImage}" />
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid MaximumRowsOrColumns="2" Orientation="Horizontal" HorizontalChildrenAlignment="Stretch" VerticalChildrenAlignment="Stretch">
</WrapGrid>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
Which gives the following result:
As seen it successfully gives 2 columns with equal width, BUT the Grid in the GridView.ItemTemlate doesn't fill the whole width of each column. I have tried setting HorizontalAlignment="Stretch" on both that Grid and on the GridView itself witout any success. Anyone has any idea of this do this?
My solution is :
<GridView ItemsSource="{Binding Results}" Margin="12"
SizeChanged="GridView_SizeChanged"
x:Name="MyGridView">
<GridView.ItemTemplate>
<DataTemplate>
<Grid>
<Image Source="{Binding SampleImage}" />
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
</Style>
</GridView.ItemContainerStyle>
</GridView>
Code Behind :
private void GridView_SizeChanged(object sender, SizeChangedEventArgs e)
{
var panel = (ItemsWrapGrid)MyGridView.ItemsPanelRoot;
panel.ItemWidth =panel.ItemHeight= e.NewSize.Width / 2;
}
You could try this:
<GridView.ItemContainerStyle>
<Style
TargetType="GridViewItem">
<Setter
Property="HorizontalAlignment"
Value="Stretch" />
<Setter
Property="VerticalAlignment"
Value="Stretch" />
</Style>
</GridView.ItemContainerStyle>
The other thing you could try is to manually set ItemWidth/ItemHeight whenever you get the SizeChanged event on the GridView.
If for some reason the above doesn't work - you could also do what I do below and update the Value of both DoubleViewModel resources on SizeChanged events:
<UserControl.Resources>
<viewModels:DoubleViewModel
x:Key="ItemWidth"
Value="120" />
<viewModels:DoubleViewModel
x:Key="ItemHeight"
Value="120" />
</UserControl.Resources>
...
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:YourItemTemplateControl
Width="{Binding Value, Source={StaticResource ItemWidth}}"
Height="{Binding Value, Source={StaticResource ItemHeight}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
Where DoubleViewModel is:
public class DoubleViewModel : BindableBase
{
#region Value
/// <summary>
/// Backing field for the Value property.
/// </summary>
private double value;
/// <summary>
/// Gets or sets a value indicating the value.
/// </summary>
public double Value
{
get { return this.value; }
set { this.SetProperty(ref this.value, value); }
}
#endregion
}
The solution I used was based in Filip Skakuns suggestion, but a slight different implementation by making a reusable User Control with this behaviour. The User Control has (among others) Columns and ItemsSource properties. I change the ItemWidth of the ItemsWrapGrid instead of the Width of the ItemTemplate and do this directly in the SizeChanged event handler.
I also needed to use a ItemsWrapGrid instead of a WrapGrid for this to work. The XAML for the final user control:
<UserControl
x:Class="MyProject.CustomControls.ColumnGridView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="ControlRoot">
<Grid DataContext="{Binding ElementName=ControlRoot}">
<GridView ItemsSource="{Binding ItemsSource}" ItemTemplate="{Binding ItemTemplate}">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid Orientation="Horizontal" SizeChanged="ItemsWrapGrid_SizeChanged" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
</Grid>
</UserControl>
And for the code-behind:
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace MyProject.CustomControls
{
public sealed partial class ColumnGridView : UserControl
{
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(ColumnGridView), new PropertyMetadata(null));
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(object), typeof(ColumnGridView), new PropertyMetadata(null));
public object ItemsSource
{
get { return (object)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(int), typeof(ColumnGridView), new PropertyMetadata(1));
public int Columns
{
get { return (int)GetValue(ColumnsProperty); }
set
{
if (value <= 0) throw new ArgumentOutOfRangeException("Columns must be greater than 0");
SetValue(ColumnsProperty, value);
}
}
public ColumnGridView()
{
this.InitializeComponent();
}
private void ItemsWrapGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
ItemsWrapGrid itemsWrapGrid = sender as ItemsWrapGrid;
if (itemsWrapGrid != null)
{
itemsWrapGrid.ItemWidth = e.NewSize.Width / Columns;
}
}
}
}
I was able to solve something very similar just binding the Item Width to the parent ActualWidth, like this:
<ListView Name="allDevicesListView" d:DataContext="{d:DesignData /SampleData/VeraServerSampleData.xaml}" ItemsSource="{Binding Devices}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Width="{Binding ElementName=allDevicesListView, Path=ActualWidth}">
<Grid Width="{Binding ElementName=allDevicesListView, Path=ActualWidth}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" FontSize="24" Grid.Column="0" Margin="0,0,14.333,0" />
<local:VeraDeviceControl VeraDeviceCategory="DimmableLight" Width="auto" Grid.Column="1"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
Alternatively you could use the Loaded-Event from your WrapGrid to set at least the ItemWidth
XAML
<Grid Background="LightGreen">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Grid.Column="0" Name="MyGrid" Background="Red">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
<ColumnDefinition Width="60" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" />
<TextBlock Grid.Column="1" Text="I.O" />
<TextBlock Grid.Column="2" Text="N.V" />
<TextBlock Grid.Column="3" Text="n.I.O" />
</Grid>
<Grid Grid.Row="0" Grid.Column="1" Background="Aqua">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
<ColumnDefinition Width="60" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" />
<TextBlock Grid.Column="1" Text="I.O" />
<TextBlock Grid.Column="2" Text="N.V" />
<TextBlock Grid.Column="3" Text="n.I.O" />
</Grid>
<GridView Grid.Row="1" Grid.ColumnSpan="2"
Background="LightBlue"
HorizontalAlignment="Stretch"
ItemsSource="{Binding Details}"
ItemContainerStyle="{StaticResource GridViewItemStyleIOhneHover}"
ItemTemplateSelector="{StaticResource MyProtokollElementDataTemplateSelector}">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid Orientation="Horizontal"
HorizontalAlignment="Stretch"
MaximumRowsOrColumns="2"
HorizontalChildrenAlignment="Stretch"
VerticalChildrenAlignment="Stretch" Loaded="MyWrapGrid_Loaded">
</WrapGrid>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
</Grid>
Codebehind
private void MyWrapGrid_Loaded(object sender, RoutedEventArgs e)
{
var wg = sender as WrapGrid;
wg.ItemWidth = MyGrid.ActualWidth;
}
Example

How do I place a border around a VariableSizedWrapGrid?

This one is driving me crazy and I am sure there must be a straightforward answer (that I haven't been able to spot).
I have a grouped gridview control which uses a VariableSizedWrapGrid for the grouped panel. The designs approved by my client include a top and bottom border on each group. I thought I could do one of two things:
Specify the border on the VariableSizedWrapGrid; or
Create a line in the GroupStyle.HeaderTemplate and apply the same to a footer.
So it seems I can't do either of those things as VariableSizedWrapGrid inherits from Panel which doesn't support the border property (only adding the border as a child element) and the GridView class doesn't include a grouped footer property. Is there a way of applying a border to the VariableSizedWrapGrid? Xaml is quite new to me as I normally specialise in server side code rather than presentation.
If I've understood you correctly they what you are trying to achieve is something like this:
This is the code for that, and it should work with a variablesizegrid as well. If I've missunderstood please add some more details and the code you already have so we can see how we can best help you.
<common:LayoutAwarePage
x:Name="pageRoot"
x:Class="App14.ItemsPage"
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App14"
xmlns:data="using:App14.Data"
xmlns:common="using:App14.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<CollectionViewSource x:Name="groups" IsSourceGrouped="true" />
</Page.Resources>
<Grid Style="{StaticResource LayoutRootStyle}">
<Grid.Resources>
<DataTemplate x:Key="groupTemplate">
<Grid>
<Border BorderBrush="White" BorderThickness="0,10" Padding="20">
<StackPanel >
<Border Background="DarkGreen" Padding="10" Margin="10">
<TextBlock Text="{Binding Name}"/>
</Border>
<Border Background="Yellow" Padding="10" Margin="10">
<Image Width="100" Height="100" Stretch="Uniform" Source="{Binding Image}"/>
</Border>
</StackPanel>
</Border>
</Grid>
</DataTemplate>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="140"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemsGridView"
AutomationProperties.Name="Items"
TabIndex="1"
Grid.RowSpan="2"
Padding="116,136,116,46"
ItemsSource="{Binding Source={StaticResource groups}}"
ItemTemplate="{StaticResource groupTemplate}">
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="10">
<TextBlock Text='{Binding Key}' Foreground="White" FontSize="25" Margin="5" />
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</GridView.GroupStyle>
</GridView>
</Grid>
and the code:
namespace App14
{
public sealed partial class ItemsPage : App14.Common.LayoutAwarePage
{
public ItemsPage()
{
this.InitializeComponent();
groups.Source = GetAllGrouped(LoadCats());
}
public IEnumerable<IGrouping<string, FakeCat>> GetAllGrouped(IEnumerable<FakeCat> cats)
{
return cats.OrderBy(x => x.Name).GroupBy(x => x.Name);
}
IEnumerable<FakeCat> LoadCats()
{
return new List<FakeCat>
{
new FakeCat {Name = "Naomi", Image = "../Assets/cat1.jpg"},
new FakeCat {Name = "Naomi", Image = "../Assets/cat2.jpg"},
new FakeCat {Name = "Peter", Image = "../Assets/cat3.jpg"},
new FakeCat {Name = "Spencer", Image = "../Assets/cat4.jpg"},
};
}
}
public class FakeCat
{
public string Name { get; set; }
public string Image { get; set; }
public int ItemSize { get; set; }
}
}
Problem solved! I guess I was having trouble figuring out what controls the templates for the group rather than the actual items. I would like to take credit for solving this, but the answer came courtesy of a member of a LinkedIn group. The following style works when applied to the GridView's GroupStyle's ContainerStyle:
<Style x:Key="GroupItemStyle1" TargetType="GroupItem">
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupItem">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentControl x:Name="HeaderContent" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}" Content="{TemplateBinding Content}" IsTabStop="False" Margin="{TemplateBinding Padding}" TabIndex="0"/>
<Rectangle VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Height="1" Fill="White" Margin="5,0,15,0" />
<ItemsControl x:Name="ItemsControl" IsTabStop="False" ItemsSource="{Binding GroupItems}" Grid.Row="1" TabIndex="1" TabNavigation="Once">
<ItemsControl.ItemContainerTransitions>
<TransitionCollection>
<AddDeleteThemeTransition/>
<ContentThemeTransition/>
<ReorderThemeTransition/>
<EntranceThemeTransition IsStaggeringEnabled="False"/>
</TransitionCollection>
</ItemsControl.ItemContainerTransitions>
</ItemsControl>
<Rectangle Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Height="1" Fill="White" Margin="5,0,15,0" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And then in the XAML for the GridView:
<GridView.GroupStyle>
<GroupStyle HidesIfEmpty="True" ContainerStyle="{StaticResource GroupItemStyle1}">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<!-- Header Template here -->
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,0,0"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</GridView.GroupStyle>