SilverLight: how to ensure ListBox completely frees its children UIElements - c#

If I have a Model like this:
public class LayerViewModel
{
public LayerViewModel(string name, string alias, UIElement representation)
{
Name = name;
Alias = alias;
Representation = representation;
}
public string Name { get; set; }
public string Alias { get; set; }
public UIElement Representation
{
get; private set;
}
}
Using the following DataTemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentControl Content="{Binding Representation, Mode=OneTime}"/>
<TextBlock
VerticalAlignment="Center"
Grid.Column="1" Text="{Binding Alias}"
FontSize="{StaticResource PhoneFontSizeLarge}"
Margin="16 21 0 20" TextWrapping="Wrap"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
If you bind a collection of these models to a ListBox, navigate away and back to the ListBox's containing page, your application will crash. This is because LayerViewModel.Representation is not released from the VisualTree even if the ListBox's ItemSource is set to null when its containing page is being unloaded. I used DependencyObject.GetVisualAncestors() to confirm this.
How do I get a ListBox to completely frees all of the UIElements that comprsises of its VisualTree? Setting its ItemsSource to null doesn't achieve this.

Related

Data binding in UWP to nested collection

I need to dig into a nested observable collection in UWP, which consists another observable collection inside it, and then bind it to my XAML.
How could I do it?
Allen Rufolo's Solution works. But Here is another way of approaching this.
x:Bind is newly implemented and available for UWP. My Answer is based on x:Bind
Sample Classes
public class MainItems
{
public string ItemName { get; set; }
public ObservableCollection<SubItems> SubItemsList { get; set; }
}
public class SubItems
{
public string SubItemName { get; set; }
}
Sample Data
ObservableCollection<MainItems> _data = new ObservableCollection<MainItems>();
for (int i = 1; i <= 5; i++)
{
MainItems _mainItems = new MainItems();
_mainItems.ItemName = "Main" + i.ToString();
_mainItems.SubItemsList = new ObservableCollection<SubItems>();
for (int j = 1; j <= 3; j++)
{
SubItems _subItems = new SubItems()
{
SubItemName = "SubItem" + i.ToString()
};
_mainItems.SubItemsList.Add(_subItems);
}
_data.Add(_mainItems);
}
My XAML
<ListView x:Name="MyMainList">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:MainItems">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="{x:Bind ItemName}" />
<ListView ItemsSource="{x:Bind SubItemsList}" Grid.Row="1">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:SubItems">
<TextBlock Foreground="Red" Text="{x:Bind SubItemName}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
x:Bind gives you an easy way to Bind your Nested Observable Collection
Output
A code example of your observable collections would help but you could do something like this...
public class MyViewModel
{
public ObservableCollection<MyObject> MyObjectCollection { get; set;}
}
public class MyObject
{
public string ObjectName {get; set;}
public ObservableCollection<AnotherObject> AnotherObjectCollection { get; set; }
}
And in your XAML you can bind to these collection similar to this
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListView x:Name="ListView1" Grid.Column="0"
ItemsSource="{Binding MyObjectCollection}">
<ListView.ItemTemplate>
<Datatemplate>
<TextBlock Text="{Binding ObjectName}"/>
</Datatemplate
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Column=1 DataContext="{Binding ElementName=ListView1, Path=SelectedItem}">
<ListView ItemsSource="{Binding AnotherObjectCollection}"/>
</Grid>
</Grid>
In this example, the DataContext of the second Grid is bound to the selected item in ListView1.
I am not sure that I get what do you need, but I guess that it could be the same as for WPF.
Check out questions and answers for the next questions:
Binding nested ItemsControls to nested collections
Nested ObservableCollection data binding in WPF
WPF Binding on Nested ItemControls with Sub Collection
Databinding for nested collections in XAML (WPF and Silverlight)

Use data template based on property

Is it possible to control which DataTemplate a node used based on a property rather than the class type which is typically seen in wpf.
In my case I have a list of teams and I want to control the background of each team being listed in the listbox as well as a few other design elements such as displaying different logos based on which team. In my mind it seems easiest to just make a data template based on the team name.
How do you guys suggest I handle it. I don't want to create a class object for every entire team. However it would be ideal if a team doesn't have a design template that a default one gets used.
Either way if someone could put together a super simple example Id appreciate it considering im not sure how to do it .
From what you described, it seems to me that those property can be hold on the Team class and you could display the content based on them !, but since you might need something much complicated you could use a DataTemplateSelector basically what you need to do is :
First : In resources area define the DataTemplates that you need plus a default one, in case none of the teams names matche a difined template,
lets say something like that :
<Window.Resources>
<DataTemplate x:Key="DefaultnDataTemplate" DataType="{x:Type YourNameSpace:Team}">
<Grid Background="LightGreen">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="DefaultnDataTemplate"/>
<TextBlock Text="{Binding Id}" Grid.Row="1" Grid.Column="0"/>
<TextBlock Text="{Binding Name}" HorizontalAlignment="Center" Grid.Row="1" Grid.Column="1"/>
<TextBlock Text="{Binding Matches}" Grid.Row="1" Grid.Column="2"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="TeamADataTemplate" DataType="{x:Type YourNameSpace:Team}">
<Grid Background="LightCoral">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="TeamADataTemplate"/>
<TextBlock Text="{Binding Id}" Grid.Row="1" Grid.Column="0"/>
<TextBlock Text="{Binding Name}" HorizontalAlignment="Center" Grid.Row="1" Grid.Column="1"/>
<TextBlock Text="{Binding Matches}" Grid.Row="1" Grid.Column="2"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="TeamBDataTemplate" DataType="{x:Type YourNameSpace:Team}">
<Grid Background="LightBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="TeamBDataTemplate"/>
<TextBlock Text="{Binding Id}" Grid.Row="1" Grid.Column="0"/>
<TextBlock Text="{Binding Name}" HorizontalAlignment="Center" Grid.Row="1" Grid.Column="1"/>
<TextBlock Text="{Binding Matches}" Grid.Row="1" Grid.Column="2"/>
</Grid>
</DataTemplate>
</Window.Resources>
Second add a DataTemplateSelector class, the class will basically check the team name and return the appropriate DataTemplate:
public class TeamDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultnDataTemplate { get; set; }
public DataTemplate TeamADataTemplate { get; set; }
public DataTemplate TeamBDataTemplate { get; set; }
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
var teamName = (item as Team).Name;
switch (teamName)
{
case "TeamA":
return TeamADataTemplate;
case "TeamB":
return TeamBDataTemplate;
default:
return DefaultnDataTemplate;
}
}
}
Third, add an instance of that class to the StaticResources, and point it to the already defined DataTemplates :
<YourNameSpace:TeamDataTemplateSelector x:Key="TeamDataTemplateSelector" TeamADataTemplate="{StaticResource TeamADataTemplate}" TeamBDataTemplate="{StaticResource TeamBDataTemplate}" DefaultnDataTemplate="{StaticResource DefaultnDataTemplate}"/>
Finally call the TemplateSelector in your List:
<ListBox ItemsSource="{Binding Teams}" ItemTemplateSelector="{StaticResource TeamDataTemplateSelector}">
</ListBox>
here the model i am using in this sample :
public class Team
{
public string Name { get; set; }
public string Id { get; set; }
public string Matches { get; set; }
}
private ObservableCollection<Team> _teams=new ObservableCollection<Team>()
{
new Team()
{
Id="1",
Matches = "45",
Name = "TeamA"
},
new Team()
{
Id="1",
Matches = "45",
Name = "TeamB"
},
new Team()
{
Id="1",
Matches = "45",
Name = "TeamC"
}
};
public ObservableCollection<Team> Teams
{
get
{
return _teams;
}
set
{
if (_teams == value)
{
return;
}
_teams = value;
OnPropertyChanged();
}
}

WPF templating with ObservableCollection

I am working on a local project and I have some issues.
I want to create a template for some results that have 3 strings(where 1 is a hyperlink) and a picture and they come as an ObservableCollection of results type binded to ItemSource.
public TestClass {
public string Title { get; set; }
public string Description { get; set; }
public string Link { get; set; }
public BitmapImage Thumbnail { get; set; }
}
So, I want to show those results in WPF and I want to use for each item a template and show them in a StackPanel (or ListView).
I tried with ListView but the only thing you can do is select the whole item, but I want also the link to be clickable.
My problem is: how can I create a template to use on each item and then add them in a list that 1 string is clickable?
As Unflux mentioned, that's a good way to do it. And as for the clickable link, use the Hyperlink control like I did below.
<ItemsControl ItemsSource="{Binding Persons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding FirstName}" Grid.Row="0" Grid.Column="0" />
<TextBlock Text="{Binding LastName}" Grid.Row="0" Grid.Column="1" />
<TextBlock Text="{Binding Age}" Grid.Row="0" Grid.Column="2" />
<TextBlock Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3">
<Hyperlink NavigateUri="{Binding BlogAddress}" Click="Hyperlink_OnClick">
<TextBlock Text="{Binding BlogAddress}" />
</Hyperlink>
</TextBlock>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
and code-behind
private void Hyperlink_OnClick(object sender, RoutedEventArgs e)
{
var link = sender as Hyperlink;
Process.Start(link.NavigateUri.ToString());
}
results in
You will probably want to style it a bit and maybe apply a different ItemsPanel to really customize the look of your collection. You can also decorate ItemsControl with scrolling.
Thanks with the idea of using ItemsControl with its template. But the Hyperlink I made it work with Click property and giving to it a:
public ICommand RunHyperlink {
get {
return new ActionCommand(this.ButtonClick);
}
}
private void ButtonClick() {
Process.Start(new ProcessStartInfo(this.Link));
}

Windows Phone - Populate ListBox with Json

I have a Json file saved in "IsolatedStorage" (json.html) and i need populate a "ListBox" with the field "FName" (from Json file).
The Json below, is stored in file (json.html) and saved in
IsolatedStorage. The file will be changed as necessary (synchronized
with web server). So I need that fields as "FNome" and "FEstado"
received information from "json.html".
My Json file:
{"xId":"52","result":{"type":"Basico.Bean.MunicipioClass.TMunicipio","id":1,"fields":{"FRefCount":0,"FId":52,"FNome":"Sumare","FEstado":"SP","FPais":"Brasil"}}}
My class from Json:
public class Fields
{
public int FId { get; set; }
public string FNome { get; set; }
public string FEstado { get; set; }
public string FPais { get; set; }
}
My ListBox:
<ListBox x:Name="listBox1"
Height="192" Width="456"
HorizontalAlignment="Center"
VerticalAlignment="Top"
BorderThickness="5"
Padding="5"
BorderBrush="White"
FontSize="30"
HorizontalContentAlignment="Stretch" Foreground="{x:Null}">
<ListBox.Background>
<SolidColorBrush Color="White" Opacity="0.5"/>
</ListBox.Background>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding FNome}"/>
<TextBlock Grid.Column="10" Text="{Binding FEstado}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'm assuming you are not using MVVM design pattern or anything "fancy", so here's (almost) minimal working example for you to study.
First, right-click on your project, select Manage NuGet Packages and install Json.NET dependency. Json.NET package helps you handle JSON formatted data.
After installation, this would be your MainWindow.xaml. Here you define your ListBox and template you want to use when showing each ListBox item.
<Window x:Class="MyWpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="100"
Width="420">
<Grid>
<!-- Your ListBox, notice the ItemsSource -->
<ListBox
Height="50"
Width="400"
FontSize="30"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding Items}">
<!-- Template to display each item -->
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- Bind UI controls to properties you want to display -->
<TextBlock Grid.Column="0" Text="{Binding FId}"/>
<TextBlock Grid.Column="1" Text="{Binding FPais}"/>
<TextBlock Grid.Column="2" Text="{Binding FNome}"/>
<TextBlock Grid.Column="3" Text="{Binding FEstado}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
And your code-behind, namely MainWindow.xaml.cs. For clarity, json is defined as const string.
using System.Windows;
using System.Collections.ObjectModel;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace MyWpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
// Your JSON string
const string json =
"{'xId':'52'," +
" 'result':{" +
" 'type':'Basico.Bean.MunicipioClass.TMunicipio'," +
" 'id':1," +
" 'fields':{" +
" 'FRefCount':0," +
" 'FId':52," +
" 'FNome':'Sumare'," +
" 'FEstado':'SP'," +
" 'FPais':'Brasil'" +
" }" +
" }" +
"}";
// Parse as JObject
JObject jObj = JObject.Parse(json);
// Extract what you need, the "fields" property
JToken jToken = jObj["result"]["fields"];
// Convert as Fields class instance
Fields fields = jToken.ToObject<Fields>();
// Assign to Items property, which is used as ListBox's ItemsSource
// In real application you'd probably want to have more than one item :)
Items = new ObservableCollection<Fields>() { fields };
}
public ObservableCollection<Fields> Items { get; set; }
}
}
And then, of course, you need you Fields class, too. You can "map" JSON to C# class autamagically if property names match. If you want to use other name in your class properties, say plain Id instead of FId, then you need to mark the property with JsonProperty attribute. For example [JsonProperty(PropetyName = "FId)].
public class Fields
{
[JsonProperty(PropertyName = "FId")]
public int FId { get; set; }
public string FNome { get; set; }
public string FEstado { get; set; }
public string FPais { get; set; }
}
Running the "application" gives you the following output

ListBox in RowDetail binding failure

I have a WPF application. It contains OrderBlock object which contains other objects, plesase see a brief view of the class.
public class OrderBlocks
{
private List<Order> _orders;
[XmlElement("tF_Transactions")]
public List<Order> Orders { get { return _orders; } set { _orders = value; OnPropertyChanged("Orders"); } }
}
public class Order : INotifyPropertyChanged
{
[XmlIgnore]
public List<Duplications> DuplicateHolder { get; set; }
}
public class Duplications
{
public string ID { get; set; }
public string Name { get; set; }
public Duplications(string newID, string newName)
{
ID = newID;
Name = newName;
}
}
I have a datagrid that is bound to my object Orders of type List Orders. My datagrid has a row detail so that when a user clicks on a row further details are displayed. I have added a listbox to this row detail. I want this row detail to show a listbox which displays my object DuplicateHolder of type List Duplications.
At the moment the listbox is empty. Please see my attempted XAML code below. Any help would be great as always.
<ListBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Name="lbIdentifier" SelectionMode="Single" DataContext="{Binding OrderBlock}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=DuplicateHolder.ID}" FontSize="10" HorizontalAlignment="Left" Margin="5,0,0,0"/>
<TextBlock Grid.Column="1" Text="{Binding Path=DuplicateHolder.Name}" FontSize="10" HorizontalAlignment="Left" Margin="5,0,0,0"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Try this
<Listbox ItemSource = {Binding DuplicateHolder}/>
and
<TextBlock Grid.Column="0" Text="{Binding Path=ID}".../>
t seems like you did not set the bindings correctly because the listbox Context should be a list of Duplications and the ItemTemplate should be for one Duplication instance from the list of duplicates. So if the global datacontext is an instance of OrderBlocks the listbox will be bound to the DuplicateHolder of an Order:
<ListBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Name="lbIdentifier" SelectionMode="Single" DataContext="{Binding Path=DuplicateHolder}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=ID}" FontSize="10" HorizontalAlignment="Left" Margin="5,0,0,0"/>
<TextBlock Grid.Column="1" Text="{Binding Path=Name}" FontSize="10" HorizontalAlignment="Left" Margin="5,0,0,0"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Categories

Resources