HorizontalTextAlignment is ignored after navigation - xamarin

I have created a simple app, where users can see more information in section, if they tab a Label, which is seen on the pictures below.
The problem is, that after navigating to AnotherPage and back, the text moves to left, even though I have set the HorizontalTextAlignment to End.
This only happens, when I have shown the section and hiding it again (by tapping on the Label twice).
The problem is illustrated on the picture below, where y is located at the left side.
My source code for showing a simple app with this problem, can be downloaded from this Dropbox link.
EDIT
Added code example
The ContentPage is as simple as
<StackLayout>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Text="Test" VerticalOptions="Start" />
<Label Text="{Binding Text}" HorizontalTextAlignment="End" TextColor="Red" HorizontalOptions="FillAndExpand" BackgroundColor="Yellow">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ChangeVisibilityCommand}" />
</Label.GestureRecognizers>
</Label>
</Grid>
<StackLayout IsVisible="{Binding IsVisible}">
<Label Text="Text 1" />
<Label Text="Text 2" />
</StackLayout>
<Button Command="{Binding OpenAnotherPageCommand}" Text="Open Another Page" />
</StackLayout>
And the ViewModel is shown below
public class MainPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ICommand _changeVisibilityCommand;
public ICommand ChangeVisibilityCommand
{
get => _changeVisibilityCommand;
set
{
if (value != _changeVisibilityCommand)
{
_changeVisibilityCommand = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ChangeVisibilityCommand)));
}
}
}
private bool _isVisible;
public bool IsVisible
{
get => _isVisible;
set
{
if (value != _isVisible)
{
_isVisible = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsVisible)));
}
}
}
private ICommand _openAnotherPageCommand;
public ICommand OpenAnotherPageCommand
{
get => _openAnotherPageCommand;
set
{
if (value != _openAnotherPageCommand)
{
_openAnotherPageCommand = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OpenAnotherPageCommand)));
}
}
}
private string _text = "u";
public string Text
{
get => _text;
set
{
if (!string.Equals(_text, value, StringComparison.InvariantCultureIgnoreCase))
{
_text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
}
public MainPageViewModel()
{
ChangeVisibilityCommand = new Command(() =>
{
IsVisible = !IsVisible;
Text = IsVisible ? "x" : "y";
});
OpenAnotherPageCommand = new Command(() =>
{
(Application.Current.MainPage as NavigationPage)?.PushAsync(new AnotherPage());
});
}
}
And the AnotherPage is simply showing a text for example

First of all, i wasn't able to reproduce your issue using the latest version of Xamarin. Maybe simply upgrading Xamarin (Forms) will remove the unwanted behavior in your case as well.
If not, you have two options:
1 - Use HorizontalOptions instead
HorizontalOptions="EndAndExpand"
since HorizontalOptions has a higher priority in the layout engine.
Not the most elegant way but surely the most reliable.
2 - Use DataBinding
In your model just add the following property:
public TextAlignment LabelAlignment
{
get => labelAlignment;
set
{
labelAlignment = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LabelAlignment)));
}
}
private TextAlignment labelAlignment = TextAlignment.End;
And in your Xaml you change HorizontalAlignment to
HorizontalTextAlignment="{Binding LabelAlignment}"
That will work because after your screen has been created, the values in the model will be loaded and updated afterwards.

Change HorizontalOptions="FillAndExpand"
To HorizontalOptions="CenterAndExpand"
This will force the text to expand from the center and be placed at the end.
To force the text to the right, you can add a another column definition to your grid:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
Place and emtpy object into the first cell, the one to the left:
number from the top (0)
number from the left (0)
and your label into the second cell, the one to the right:
number from the top (0)
number from the left (1)
The text area will adjust to the text length and will always be to the right.
I hope this information helps!
You can read more about grid in the documentation:
https://learn.microsoft.com/en-us/xamarin/android/user-interface/layouts/grid-layout

Related

BindableProperty of type IList<View> with contents set in XAML is never updated

I am making a custom ContentView for a common container type I use throughout my app. The custom view has two bindable properties: the header (string) to be displayed in the upper left corner, and a list of buttons (List) to be displayed in the upper right corner.
The header property (and the Contents property) works as expected, but the buttons property does not work at all. The ButtonsPropertyChanged does never fire, and there is no binding error message in the output console. What am I doing wrong?
HomePage.xaml:
<controls:SectionContainer Header="Text blah"> <!-- Header works -->
<controls:SectionContainer.Buttons>
<!-- This does NOT work - buttons are not shown -->
<ImageButton Source="{Binding SomeIcon}"></ImageButton>
<ImageButton Source="{Binding AnotherIcon}"></ImageButton>
</controls:SectionContainer.Buttons>
<!-- Actual contents here -- this works, the contents are shown -->
</controls:SectionContainer>
SectionContainer.xaml:
<ContentView.Content>
<Frame Style="{StaticResource SectionFrame}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Label x:Name="HeaderLabel" Grid.Column="0" Grid.Row="0" VerticalTextAlignment="Start" VerticalOptions="Start" Style="{StaticResource Header}"></Label>
<StackLayout x:Name="ButtonsContainer" Grid.Row="0" Grid.Column="1" Orientation="Horizontal" HorizontalOptions="End"></StackLayout>
<StackLayout Grid.Column="0" Grid.Row="1" x:Name="Container" Style="{StaticResource Section}"></StackLayout>
</Grid>
</Frame>
</ContentView.Content>
SectionContainer.xaml.cs:
[XamlCompilation(XamlCompilationOptions.Compile)]
[ContentProperty("Contents")]
public partial class SectionContainer : ContentView
{
public IList<View> Contents => Container.Children;
public string Header
{
get => (string)GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
public static readonly BindableProperty HeaderProperty = BindableProperty.Create(
nameof(Header),
typeof(string),
typeof(SectionContainer),
"",
propertyChanged: HeaderPropertyChanged);
private static void HeaderPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
((SectionContainer) bindable).HeaderLabel.Text = (string) newvalue;
}
public IList<View> Buttons
{
get => (IList<View>)GetValue(ButtonsProperty);
set => SetValue(ButtonsProperty, value);
}
public static readonly BindableProperty ButtonsProperty = BindableProperty.Create(
nameof(Buttons),
typeof(IList<View>),
typeof(SectionContainer),
new List<View>(),
BindingMode.OneWay,
propertyChanged: ButtonsPropertyChanged);
private static void ButtonsPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
Debug.WriteLine(newvalue); // This is never printed, and there are no error messages
var children= ((SectionContainer)bindable).ButtonsContainer.Children;
children.Clear();
foreach (var child in (IList<View>) newvalue)
children.Add(child);
}
public SectionContainer()
{
InitializeComponent();
}
}
Xamarin.Forms doesn't set the property, it adds elements to the object created as default value.
Therefore, an ObservableCollection created using defaultValueCreator (instead of defaultValue), where we listen for changes and populate the user control seems to work. Not sure if we need the propertyChanged event at all, or if we will ever need to handle any other ObservableCollection events than single element adds, but I kept it in just in case.
public static readonly BindableProperty ButtonsProperty = BindableProperty.Create(
nameof(Buttons),
typeof(IList<View>),
typeof(SectionContainer),
propertyChanged: ButtonsPropertyChanged,
defaultValueCreator: CreateObservableList);
private static object CreateObservableList(BindableObject bindable)
{
var oc = new ObservableCollection<View>();
// It appears Xamarin.Forms doesn't create a list containing the Views from the XAML, it uses the default value (list),
// and adds the Views one by one. Therefore, we create the default value as an ObservableCollection, then we listen
// for changes to this collection.
oc.CollectionChanged += (o, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
// If this is an Add operation, simply add the view to the StackLayout.
var children = ((SectionContainer) bindable).ButtonsContainer.Children;
foreach (var child in e.NewItems.Cast<View>())
children.Add(child);
}
else
{
// Otherwise, recreate the StackLayout by clearing it, and adding all children all over again.
// (Won't bother handling all cases, since the normal one seems to be Add anyway.)
ButtonsPropertyChanged(bindable, null, o);
}
};
return oc;
}

Is there any way that I can speed up the adding of new elements to a page when using the C# back end?

I have code that works but I notice it's rather slow to create the page elements.
Here's what I have so far. Note that I'm not adding everything at once as I found that when I did the page creation was even slower.
public void CreateSwitchSection(bool? selected)
{
Application.Current.Resources.TryGetValue("FrameBorder", out object frameBorder);
var st = new StackLayout { Orientation = StackOrientation.Vertical, Spacing = 0 };
st.Children.Add(AddSwitchRows(selected, App.cardSetWithWordCount.Take(20)));
st.Children.Add(AddSwitchRows(selected, App.cardSetWithWordCount.Skip(20).Take(20)));
st.Children.Add(AddSwitchRows(selected, App.cardSetWithWordCount.Skip(40).Take(20)));
st.Children.Add(AddSwitchRows(selected, App.cardSetWithWordCount.Skip(60).Take(20)));
st.Children.Add(AddSwitchRows(selected, App.cardSetWithWordCount.Skip(80).Take(20)));
var fr = new Frame { Style = (Style)frameBorder };
var fs = new FrameStack { };
var ht = new HeaderTemplate()
{
Text = "CHOOSE CARD SETS FOR THE DECK"
};
fs.Children.Add(ht);
fs.Children.Add(st);
fs.Children.Add(new LineTemplate());
fr.Content = fs;
details.Children.Clear();
details.Children.Add(fr);
}
private StackLayout AddSwitchRows(bool? selected, IEnumerable<CardSetWithWordCount> data)
{
var stack = new StackLayout
{
Orientation = StackOrientation.Vertical,
Spacing = 0
};
foreach (var x in data)
{
var cell = new BadgeGridTemplate
{
BindingContext = x,
Text = x.Name,
State = selected == true ? "E" : "D",
Message = x.TotalWordCount.ToString(),
TapCommand = (Command)vm.SelectCardSetCmd,
RowId = x.Id,
Separator = true
};
stack.Children.Add(cell);
}
return stack;
}
For reference here is the BadgeGridTemplate I coded:
<?xml version="1.0" encoding="UTF-8"?>
<t:BaseGridTemplate xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:t="clr-namespace:Japanese.Templates"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Japanese;assembly=Japanese"
xmlns:b="clr-namespace:Behaviors;assembly=Behaviors"
xmlns:converters="clr-namespace:Japanese.Converters;assembly=Japanese"
x:Class="Japanese.Templates.BadgeGridTemplate"
x:Name="this"
HeightRequest="{DynamicResource GridHeight}" Margin="0"
Orientation="Vertical" Spacing="0">
<BoxView HeightRequest="1" HorizontalOptions="FillAndExpand" IsVisible="{Binding Separator, Source={x:Reference this}}" BackgroundColor="{DynamicResource LineColor}" Margin="0" />
<Grid Padding="20,0" VerticalOptions="CenterAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Text="{Binding Text, Source={x:Reference this}}" TextColor="{DynamicResource LabelColor}" Style="{StaticResource LabelText}" VerticalTextAlignment="Center" WidthRequest="30" />
<t:Button Grid.Column="1" Meta="GsT" RowId="{Binding RowId, Source={x:Reference this}}" State="{Binding State, Source={x:Reference this}}" TapCommand="{Binding TapCommand, Source={x:Reference this}}" Text="{Binding Message, Source={x:Reference this}}" Theme="{Binding Theme}" WidthRequest="30" />
</Grid>
</t:BaseGridTemplate>
What are we trying to do:
Take a list of data from some source, Put them in a list
What your code is doing:
taking all the data building(all 100 AddSwitchRows ) UI right then.
Those hundreds of UI has to be rendered all at once on the screen. (even if the user is not going to look at them all)
Suggestion: How should we do this:
I highly suggest using a Listview.
Or FlowListview for grids https://github.com/daniel-luberda/DLToolkit.Forms.Controls
Why?
Listview will only try to draw UI for the screen user is looking at.
If there are a thousand more items needed. It will be built only when the user scrolls down to that portion.
If you want to make multiple kinds of cells which depends on the data you receive we should use DataTemplate with ListView
More Information on Microsoft's Official Docs
Example
I have added 1000 items in the listView's ItemSource when clicked on the button in header.
You can add your template in the Listview.datatemplate tag and bind your ViewModel to this view
View
And if you want to change item's view as per a property value. Use DataTemplate selecter property of ListView
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:stackAnswerApp"
x:Class="stackAnswerApp.MainPage"
x:Name="Root">
<ContentPage.BindingContext>
<local:MainPageViewModel />
</ContentPage.BindingContext>
<ListView ItemsSource="{Binding ListItems}" RowHeight="100">
<ListView.Header>
<Button Text="Start Adding" Command="{Binding StartAddingItems}" />
</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<!-- whatever is your template -->
<StackLayout BackgroundColor="Aqua">
<Label Text="{Binding Text1}" />
<Button BackgroundColor="Blue"
Text="Go" BindingContext="{Binding Source={x:Reference Root},Path=BindingContext}"
Command="{Binding ButtonTapped}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
ViewModel
class AModel
{
public string Text1 { get; set; }
public string Text2 { get; set; }
}
class MainPageViewModel
{
public ObservableCollection<AModel> ListItems { get; set; }
private const int TotalRows = 1000;
public ICommand StartAddingItems
{
get
{
return new Command(async () =>
{
//add too many items in the list
await Task.Run(async () => { await AddItemsToList(); });
});
}
}
private async Task AddItemsToList()
{
for (var i = 0; i < TotalRows; i++)
{
ListItems.Add(new AModel() {Text1 = $"index {i}", Text2 = $"tap {i}"});
}
}
public ICommand ButtonTapped
{
get
{
return new Command((() =>
{
//button in the list was tapped
}));
}
}
public MainPageViewModel()
{
ListItems = new ObservableCollection<AModel>();
}
}
You can try using BatchBegin() on stacks variable before the loop of child adds in your function AddSwitchRows and BatchCommit() after the loop. And if that works, do the same to your parent stack variable st in CreateSwitchSection.
Ie.
stacks.BatchBegin();
foreach var x in data
{
…
}
stacks.BatchCommit();
This may resolve it as many languages displaying forms like to recalculate layout every time (expensive) the collection/list/children are added/removed/updated. Batching allows the layout recalculations to be done once instead of every time the list changes.
If the order matters, I haven't seen another way to do this.
If the order doesn't matter, You could split the StackLayout in to multiple StackLayouts and just add the individual elements inside asynchronous threads using Task.WhenAll.
Task.WhenAll is like having multiple people do work for you at the same time, instead of just 1 person.
I think Problem is in
App.cardSetWithWordCount
It may be a linq query
I think this goes to db every time .Just run it once and save it on variable or property
var globalApp_cardSetWithWordCount = App.cardSetWithWordCount.ToList()
Linq is executed when to list or toarray is called.
List<int> ints = new List<int>();
foreach(var y in query)
ints.Add(y.Count());
return ints.ToArray();
then use same take and skip format

Xamarin: Binding a ContentView content not working, wrong implementation

I need help understanding how I can bind a ContentView's Content's to my Xamarin page. I have tried maybe 20 different methods and I can only get the binded contentview to render when I don't use bindings and set the property directly.
<ContentPage.Content>
<Grid VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" x:Name="GridLayout" RowSpacing="0" ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<nav:NavView Grid.Row="0" Grid.Column="0" Padding="0,0,0,0" Margin="0,0,0,0" HorizontalOptions="FillAndExpand" VerticalOptions="StartAndExpand"
HeightRequest="60" x:Name="nav"/>
<ContentView BindingContext="{Binding MainView}" Grid.Row="1" Grid.Column="0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
</Grid>
</ContentPage.Content>
and then below is my code behind
public partial class DetailLayoutView : ContentPage, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
private ContentView mainView;
public ContentView MainView
{
get { return mainView; }
set
{
mainView = value;
OnPropertyChanged("MainView");
}
}
public DetailLayoutView()
{
InitializeComponent ();
MainView = new PyxusChatView();
BindingContext = MainView;
}
}
Can someone help guide me to the right direction? I dont have the bandwidth to implement an entire MVVM refactor at this time I would just like to know how I can achieve this w/ minimal code.
DETECT WHEN A UI IS RENDERED IOS & ANDROID BELOW...
public DetailLayoutView()
{
InitializeComponent ();
MainContentArea.LayoutChanged += MainContentArea_LayoutChanged;
}
private void MainContentArea_LayoutChanged(object sender, EventArgs e)
{
Device.BeginInvokeOnMainThread(() => {
//UI Is now visible, send a msg to let everyone subscribed to "UpdateDetailView" event now that the
//detail page was changed and it is now okay to make API calls!
MessagingCenter.Send<ContentView, string>(MainView, "UpdateDetailView", "From BlePairingViewModel");
});
}
YOU HAVE SENT MSG NOW YOU CAN MAKE API CALLS & YOUR UI IS VISIBLE W/O MVVM... ALTHOUGH MVVM WOULD BE A BETTER PRACTICE TO INDIVIDUALS W/ MORE TIME TO CODE..
MessagingCenter.Subscribe<ContentView, string>("Pyx", "UpdateDetailView", (sender, arg) =>
{
if (sender == this)
{
Task.Run(async () =>
{
await OnAppearing();
});
}
});
I AM NOT ADVISING THIS AS BEST PRACTICE BUT IT DOES ANSWER THE QUESTION

What's the optimal way to show a check mark against four settings with forms?

I have a screen that shows four rows: "English", "Romaji", "Kana" and "Kanji". The user can select one of the four and when selected a check mark will appear next to it. Currently it's implemented like this (just showing the first two):
englishSide.GestureRecognizers.Add(new TapGestureRecognizer
{
NumberOfTapsRequired = 1,
Command = new Command(() =>
{
englishImage.IsVisible = true;
romajiImage.IsVisible = false;
kanaImage.IsVisible = false;
kanjiImage.IsVisible = false;
})
});
romajiSide.GestureRecognizers.Add(new TapGestureRecognizer
{
NumberOfTapsRequired = 1,
Command = new Command(() =>
{
englishImage.IsVisible = false;
romajiImage.IsVisible = true;
kanaImage.IsVisible = false;
kanjiImage.IsVisible = false;
})
});
As the number of choices increases from 4 to more I can see this would get a lot more complicated. Is there a more optimal way to do this?
For reference here's the Xaml:
<TableSection Title="Card Front Side">
<ViewCell>
<StackLayout x:Name="englishSide" Padding="20,0,20,0"
Orientation="Horizontal" VerticalOptions="Center">
<Label Text="English" XAlign="Center"/>
<Image x:Name="englishImage" Source="check.png"
HorizontalOptions="EndAndExpand" IsVisible="false" />
</StackLayout>
</ViewCell>
<ViewCell>
<StackLayout x:Name="romajiSide" Padding="20,0,20,0"
Orientation="Horizontal" VerticalOptions="Center">
<Label Text="Romaji" XAlign="Center"/>
<Image x:Name="romajiImage" Source="check.png"
HorizontalOptions="EndAndExpand" IsVisible="false" />
</StackLayout>
</ViewCell>
What you would need is a proper viewmodel pattern where the ListView.ItemsSource is an IEnumerable<YourViewModel>. I use Prism for Xamarin.Forms to facilitate this.
Read more:
https://msdn.microsoft.com/en-us/library/ff798384.aspx?f=255&MSPPError=-2147217396
https://github.com/PrismLibrary/Prism
YourViewModel will be the child viewmodel containing all the logic and ecapsulating the business object.
public class MyViewModel
{
// INotifyPropertyChanged would be needed
public MyModel Model {get;set;}
public bool IsChecked {get;set;}
public string Image => this.IsChecked? "theImage.jpg" : null;
}
Hope that helps.

Windows Phone 7 Listbox not displaying

So I have a listbox I am trying to make for an "achievement page". Everything works good if I use a List but when I switch it to a List there is nothing displayed, not even from the xaml...
public partial class achievementPage : PhoneApplicationPage
{
public string Description { get; set; }
public string Type { get; set; }
public achievementPage()
{
InitializeComponent();
loadListbox();
}
public achievementPage(string achievementGet, string d1)
{
}
public void loadListbox()
{
achievementStoreage.loadData();
List<achievementPage> achievementList = new List<achievementPage>();
achievementList.Add(new achievementPage(achievementStoreage.achievement1, "This is a test"));
achievementList.Add(new achievementPage(achievementStoreage.achievement2, "This is another test"));
//List<string> achievementList = new List<string>();
//achievementList.Add("Sup");
achievementListBox.ItemsSource = achievementList;
}
}
<ListBox Name="achievementListBox" Margin="0,0,0,0" >
<ListBox.ItemTemplate>
<DataTemplate>
<Button Width="776" Height="120" BorderBrush="Black">
<Button.Content>
<StackPanel Orientation="Horizontal" Height="50">
<StackPanel Orientation="Horizontal" Height="40">
<TextBlock Width="150" Foreground="Black" FontSize="22" Text="Description:" Height="40"/>
</StackPanel>
</StackPanel>
</Button.Content>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
All I get is a blank page.. P.S don't worry about achievementStoreage, it is working properly.(just where I get stored data)
Frankly, it seems that you are simply not allowed to use ItemTemplates along with UIElements as the items' DataContexts. I've tried with:
achievementListBox.ItemsSource = new [] {"a","b"};
and both items were visible and printed dummy "description" texts, but none of the following lines I've tried has presented anything at all:
achievementListBox.ItemsSource = new [] { new TextBlock(), new TextBlock()};
achievementListBox.ItemsSource = new [] { new Rectangle(), new Rectangle()};
achievementListBox.ItemsSource = new [] { new Grid(), new Grid()};
Trying with your custom page - the same. No items shown.
This is very misleading. The items were shown, but look at the lines above: the controls were created empty, with no contents set!.
It turns out, that if the ListBox detects that the Item is an UIElement, then it does not use the ItemTemplate, but it presents that UIElement directly!
achievementListBox.ItemsSource = new[] { new TextBlock() { Text = "bbb" }, new TextBlock() { Text = "eee" } };
achievementListBox.ItemsSource = new[] { new Rectangle() { Fill = new SolidColorBrush(Colors.Red), Width = 30, Height = 10 }, new Rectangle() { Fill = new SolidColorBrush(Colors.Green), Width = 30, Height = 10 } };
var gridA = new Grid() { Width = 110, Height = 40 }; gridA.Children.Add(new Rectangle() { Fill = new SolidColorBrush(Colors.Red) });
var gridB = new Grid() { Width = 110, Height = 40 }; gridB.Children.Add(new Rectangle() { Fill = new SolidColorBrush(Colors.Green) });
achievementListBox.ItemsSource = new[] { gridA, gridB };
All of the three above examples completely ignore the ListBox.ItemTemplate, and instead, they display two items directly: two textboxes, two rectangles, two larger rectangles (in a Grid).
Getting back to your case: It means that with your original setup, the ListBox would try to display the items directly too - as your custom Page is a UIElement. And indeed it did that! But your pages were ... empty. In the overloaded constructor you omitted the InitializeComponent(); that constructs the View by reading the XAML code. Here is a corrected example that displays a "Hello" three times: Once just because it lies on the page, and next two times because the ListBox rows are set to the same page.
Please excuse me for renaming the classes, I simply started a new project instead of pasting your code.
Please note that I had to add to the XAML some other controls, because the Pages used as the data items would be display as empty, because they would have no items set
public partial class MainPage : PhoneApplicationPage
{
public string Description { get; set; }
public string Type { get; set; }
public MainPage()
{
InitializeComponent();
loadListbox();
}
public MainPage(string achievementGet, string d1)
{
InitializeComponent();
someText.Text = d1;
}
public void loadListbox()
{
achievementListBox.ItemsSource = new[] { new MainPage(null, "ddd"), new MainPage(null, "ccc") };
}
}
<StackPanel>
<TextBlock>
<Run Text="Hello" />
<Run Text=" " />
<Run x:Name="someText" />
</TextBlock>
<ListBox Name="achievementListBox" Margin="0,0,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Width="150" Foreground="White"
FontSize="22" Text="This DataTemplate is IGNORED because the Item is UIElement"
Height="40"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
I tried to shape the code in a similar manner to yours, just removed some lines not relevant to the problem. I hope this explains you everything now :)
Oh my. Why do create a list of achievementPages? On your achievementPage you will want to have a ListBox with items of type, like, AchievementItem, CompletedAchievement, NotCompletedAchievement, etc.
Currently, nothing shows up because your code probably throws StackoverflowException (not joking here:)). Look: your achievementPage constructor calls loadListBox that creates two achievementPages and adds them to the list. But creating two achievementPages causes again their constructors to be called two times, which invokes loadListBox two times, and so on..
-- edit: ok, no stackoverflow, I've just noticed the second constructor. You should stick to naming the classes with capital letters you know :) Anyways, putting a Page as a data item of a ListBox on a Page is a bad idea.
What you wanted to get should look more like:
public partial class AchievementPage : PhoneApplicationPage
{
public string Description { get; set; }
public string Type { get; set; }
public AchievementPage()
{
InitializeComponent();
loadListbox();
}
public void loadListbox()
{
var theList = new List<Achievement>();
theList.Add(new Achievement{ AchvCount=3, AchvName="medals" });
theList.Add(new Achievement{ AchvCount=2, AchvName="badges" });
theList.Add(new Achievement{ AchvCount=6, AchvName="zonks" });
achievementListBox.ItemsSource = achievementList;
}
}
public class Achievement : DependencyObject
{
public int AchvCount {get; set;}
public string AchvName {get; set;}
}
<ListBox Name="achievementListBox" Margin="0,0,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="You've got:" />
<TextBlock Grid.Column="0" Text="{Binding AchvCount}" />
<TextBlock Grid.Column="0" Text="{Binding AchvName}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Resources