I've read a number of posts on binding Dictionary to WPF ListView and ListBox but I can't get equivalent code to work in WinRT.
<Grid Margin="10" Width="1000" VerticalAlignment="Stretch">
<ListBox Name="StatListView" ItemsSource="{Binding FooDictionary}" >
<ListBox.ItemTemplate>
<DataTemplate >
<Grid Margin="6">
<StackPanel Orientation="Horizontal" >
<TextBlock Text="{Binding Key}" Margin="5" />
<TextBlock Text="{Binding Value}" Margin="5" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
public Dictionary<string, string> FooDictionary
{
get
{
Dictionary<string, string> temp = new Dictionary<string, string>();
temp.Add("key1", "value1");
temp.Add("key2", "value2");
temp.Add("key3", "value3");
temp.Add("key4", "value4");
return temp;
}
}
What is the proper binding?
The error in the output window is (trimmed to the most useful part):
Error: Cannot get 'Key' value (type 'String') from type
'System.Runtime.InteropServices.WindowsRuntime.CLRIKeyValuePairImpl`2
[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, ....
Internally, WinRT is converting the type to:
System.Runtime.InteropServices.WindowsRuntime.CLRIKeyValuePairImpl<K, V>
If you add to your DataTemplate:
<TextBlock Text="{Binding}" Margin="5" />
You'll see that it emits the above type with String, String.
However, for some reason, it's not being properly handled as expected. If you search for that type on the Internet, you'll see that there's a documented bug for the issue on Connect.
A simple work around would be to place your data in a simple object that is not a KeyValuePair:
List<StringKeyValue> temp = new List<StringKeyValue>();
temp.Add(new StringKeyValue { Key = "key1", Value = "value1" } );
temp.Add(new StringKeyValue { Key = "key2", Value = "value2" });
temp.Add(new StringKeyValue { Key = "key3", Value = "value3" });
temp.Add(new StringKeyValue { Key = "key4", Value = "value4" });
this.DefaultViewModel["FooDictionary"] = temp;
public class StringKeyValue
{
public string Key { get; set; }
public string Value { get; set; }
}
As an aside, from a simple test at least, it's not the Dictionary that's causing the issue at all, it's the fact that it's a KeyValuePair object instance that's being converted to the CLRIKeyValuePairImpl type mentioned above. I tried just using a list and adding a KeyValuePair<string, string> instance to a List, and that failed as well.
I've come up with a workaround, that involves generating your own Key Value pairs dynamically.
If you've specialized Dictionary, just add this:
public IEnumerable<MyPair<K, V>> Collection
{
get {
foreach (var v in this)
{
MyPair<K, V> p = new MyPair<K, V>() { Key = v.Key, Value = v.Value };
yield return p;
}
}
}
and define your Pair type:
public class MyPair<K, V>
{
public K Key { get; set; }
public V Value { get; set; }
}
Also, be careful that you create a new object each time. Some items walk across the object, and store the return, which can lead to everything looking like the last item, if you try to reuse the MyPair like I originally did.
Related
I am reading in JSON and then displaying it in a WPF treeview.
Here is the code...
Class MainWindow
Public Sub New()
InitializeComponent()
Dim dic = GetThreadedObject(GetJASN())("phases")
Dim items = dic(0)
tView.ItemsSource = items
End Sub
Private Function GetJASN() As String
Dim output As String = My.Computer.FileSystem.ReadAllText(My.Application.Info.DirectoryPath & "\UAL525 Phase of Flight.json")
Return output
End Function
Private Function GetThreadedObject(JASN As String)
Dim Js As New JavaScriptSerializer()
Js.MaxJsonLength = JASN.Length * 2
Dim j = Js.Deserialize(Of Object)(JASN)
Return j
End Function
End Class
And the WPF...
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<TreeView x:Name="tView">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Value}" >
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Foreground="Red"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding Key}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
Start and End points (above) look fine (presumably because they contain child elements to display).
But the Phase element should just contain one value. A single string that reads "GROUND". But it is broken up into a charArray for some reason. And displayed in multiple elements as shown above.
So what is the key to fixing this? Multiple data templates that display a string differently from other objects?
Here is the code Rekshino submitted, in Vb.
Imports System.Globalization
Public Class ValConv
Implements IValueConverter
Private Function IValueConverter_Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
If (TypeOf value Is String) Then
Dim newStr As New List(Of String)
newStr.Add(value)
Return newStr
Else
Return value
End If
End Function
Private Function IValueConverter_ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
Return value
End Function
End Class
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp2"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<local:ValConv x:Key="valConv"/>
</Window.Resources>
<Grid>
<TreeView x:Name="tView">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Value, Converter={StaticResource valConv}}" >
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Foreground="Red"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding Key}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
The problem is that your XAML can only show a collections in dictionary's value and if there is a string, then it will be considered as collection of characters. One of the quick sollutions is to create a converter, which will transform your strings into string collections.
For this you need a value converter(sorry I do code in c#)
public class ValConv : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string str)
{
return new List<string> { str };
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
Instantiate this converter in resources:
<Window.Resources>
<local:ValConv x:Key="valKonv"/>
</Window.Resources>
and use it:
<HierarchicalDataTemplate ItemsSource="{Binding Value, Converter={StaticResource valConv}}" >
I needed a more generic operation to consume any JSON.
This code uses the nuget Newtonsoft JSON to do the magic of taking any raw JSON (without models) and loading it into a TreeView which looks like this:
JSON
string jsonString = #"[{""BatchId"":0,""AccessionChanges"":[{""LabId"":8675309,""InstanceChanges"":[{""Property"":""Note"",""ChangedTo"":""Jabberwocky"",""UniqueId"":null,""SummaryInstance"":null},{""Property"":""Instrument"",""ChangedTo"":""instrumented"",""UniqueId"":null,""SummaryInstance"":null}],""DetailChanges"":[{""Property"":""Comments"",""ChangedTo"":""2nd Comment"",""UniqueId"":null,""SummaryInstance"":null},{""Property"":""CCC"",""ChangedTo"":""XR71"",""UniqueId"":null,""SummaryInstance"":null}]}]}]";
Xaml <TreeView x:Name="tView" />
Codbehind Xaml
InitializeComponent();
try
{
tView.Items.Add(JSONOperation.Json2Tree(JArray.Parse(jsonString), "Root"));
}
catch (JsonReaderException jre)
{
MessageBox.Show($"Invalid Json {jre.Message}");
}
public static class JSONOperation
public static TreeViewItem Json2Tree(JToken root, string rootName = "")
{
var parent = new TreeViewItem() { Header = rootName };
foreach (JToken obj in root)
foreach (KeyValuePair<string, JToken> token in (JObject)obj)
switch (token.Value.Type)
{
case JTokenType.Array:
var jArray = token.Value as JArray;
if (jArray?.Any() ?? false)
parent.Items.Add(Json2Tree(token.Value as JArray, token.Key));
else
parent.Items.Add($"\x22{token.Key}\x22 : [ ]"); // Empty array
break;
case JTokenType.Object:
parent.Items.Add(Json2Tree((JObject)token.Value, token.Key));
break;
default:
parent.Items.Add(GetChild(token));
break;
}
return parent;
}
private static TreeViewItem GetChild(KeyValuePair<string, JToken> token)
{
var value = token.Value.ToString();
var outputValue = string.IsNullOrEmpty(value) ? "null" : value;
return new TreeViewItem() { Header = $" \x22{token.Key}\x22 : \x22{outputValue}\x22"};
}
I have a listbox and its datacontext is set to a collection. In the background I am fetching data and giving it to the binding element. But it is not reflected in my design. Code goes as follows.
<ListBox Name="StatusListBox"
DataContext="{Binding StatusCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding}" TextWrapping="Wrap"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the Page definitions I gave as
DataContext="{Binding StatusViewModel, RelativeSource={RelativeSource Self}}"
d:DataContext="{Binding Source={d:DesignData Source=/Data/SampleData.json, Type=data:DataSource}}"
In cs file I have a statusviewmodel and i give the statusviewmodel, the content i fetched.
public ObservableDictionary StatusViewModel
{
get
{
return this.statusViewModel;
}
}
private async void GetStatusOnline()
{
HttpWebRequest request = HttpWebRequest.Create(requestURI) as HttpWebRequest;
WebResponse response = await request.GetResponseAsync();
ObservableCollection<string> statusCollection = new ObservableCollection<string>();
using (var reader = new StreamReader(response.GetResponseStream()))
{
string textcontent = reader.ReadToEnd();
Debug.WriteLine(textcontent);
DataSource dataSource = new DataSource();
this.StatusViewModel["StatusCollection"] = dataSource.GetStatusCollection(textcontent);
}
}
private ObservableCollection<Status> _statusCollection = new ObservableCollection<Status>();
public ObservableCollection<Status> StatusCollection
{
get
{
return this._statusCollection;
}
}
public IEnumerable<Status> GetStatusCollection(string textcontent)
{
JsonObject jsonObject = JsonObject.Parse(textcontent);
JsonArray jsonArray = jsonObject["items"].GetArray();
foreach (JsonValue value in jsonArray)
{
StatusCollection.Add(new Status(value.GetString()));
}
return StatusCollection;
}
The sample json data is
{
"items": [
"Hi\n",
"This is my new status. How is this?\n",
"This is thrid line."
]
}
You are not settings the ItemSource property of the ListBox. Change
DataContext="{Binding StatusCollection}"
to
ItemSource="{Binding StatusCollection}"
Okay, I'll take a swing at this.
Looking at your XAML code for the Listbox.ItemTemplate and the DataContext,
you've set the data context to be Binding StatusCollection, and in your page definitions you have the DataContext as the Binding StatusViewModel. As well as d:DataContext is {Binding Source={d:DesignData Source=/Data/SampleData.json, Type=data:DataSource}
The issue lies in the Listbox's ItemTemplate for the TextBlock. Since you have the text set as {Binding} which is shorthand, it is assuming that the DataContext is for StatusCollection. In reality, the datacontext you want is for the DesignData source for the JSON file.
So I would suggest changing your StatusViewModel to reflect calling the JSON file, otherwise you have disjointed code whereas your JSON file is never actually read.
I'm saving some data in a database using linq. I have a DAO class, where I put all my CRUD methods, and I have another class where I define my table and its columns.
[Table(Name = "CONTACTS")]
public class UserContacts
{
private int _id;
[Column(Name = "ID", IsPrimaryKey = true, IsDbGenerated = true, CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int ID
{
get { return _id; }
set { _id = value; }
}
private string _name;
[Column(Name = "NAME", CanBeNull = false)]
public string NAME
{
get { return _name; }
set { _name = value; }
}
private string _number;
[Column(Name = "NUMBER", CanBeNull = false)]
public string NUMBER
{
get { return _number; }
set { _number = value; }
}
}
DAO Class
public class DAOUserContacts
{
public IList<UserContacts> list = new List<UserContacts>();
public IList<UserContacts> GetData()
{
try
{
using (DataBaseContext db = new DataBaseContext(DataBaseContext.ConnectionString))
{
list = (from info in db.UserContacts orderby info.COLUMN_NAME ascending select info).ToList();
}
return list;
}
catch (Exception)
{
return null;
}
}
// Other methods
}
I'm showing all data stored in this database in a LongListSelector.
In Main.cs constructor
llsContacts.DataContext = new DAORUserContacts().GetData();
In Main.xaml
<phone:LongListSelector x:Name="llsContacts" ItemsSource="{Binding}" >
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<ListBoxItem local:TiltEffect.IsTiltEnabled="true">
<Grid Margin="0" >
<TextBlock Text="{Binding NAME}" FontFamily="Segoe WP" FontSize="28" Margin="77,0,0,0" />
<TextBlock Text="{Binding NUMBER}" FontFamily="Segoe WP Light" FontSize="17" Margin="77,33,0,0"/>
</Grid>
</ListBoxItem>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
I made a research on internet, and found about ObservableCollection, and I want to use it. I read this article http://www.codeproject.com/Articles/80555/Databinding-in-Silverlight-applications#_Toc261149245 to understand how it works, but I still have some doubts.
On this article, was created an Employee that inherits from INotifyPropertyChanged, and an Emplyees that inherits from ObservableCollection.
In my case, I want that my list (on xaml) always shows what is in database.
Is OK inheriting my UserContacts from INotifyPropertyChanged? Because this class declares a Table...
And should my DAOClass inherits from ObservableCollection? Or just my GetData() returns a ObservableCollection instead of a List?
Another thing.. there is a button that, when the user press, updates the database (search all the contacts on device, and remove from database what had been removed in phone, and add to database what had been add in phone).
Using ObservableCollection, when the search for contacts finishes, and my database is updated, should I call something to update the LongListSelector on xaml? Or after the database update, the LongListSelector is automatically updated too?
Thanks...
User viewmodel for assigning data context and assign observable collection at xaml side as binding.And viewmodel should have observable collection which you use for binding.Then if you made any changes for the collection automatically updates the longlistselector.
Every time you should change the collection according to your database changes.
Can anybody please tell me how can I parse the below Feed
http://teluguone.com/movies/moviesXml.php
In MainPage I will Arrange 8 Buttons like Action,
Comedy,
Love etc as based on different categories given in above feed.
When I click Action Button.The movies present in the Action Category has to be displayed in listbox.
And similarly when clicked on the other buttons also movies related to that particular categories has to be displayed in ListBox.
Anybody please give me hint how can I parse this feed with different categories.I am not understanding how can I parse these different categories.
I have written the following code to download xml file.Does it works.please tell me.
MainPage.Xaml:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox x:Name="moviesList" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="130">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image delay:LowProfileImageLoader.UriSource="{Binding MovieImage}"
Grid.Column="0"
Width="97"
Height="125"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="6"/>
<StackPanel Margin="10,15,0,0"
Grid.Column="1"
Height="60"
Orientation="Horizontal"
VerticalAlignment="Top">
<TextBlock Text="{Binding MovieName}"
FontSize="30" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Mainpage.xaml.cs:
namespace PhoneApp1
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
// is there network connection available
if (!System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
{
MessageBox.Show("No network connection available!");
return;
}
// start loading XML-data
WebClient downloader = new WebClient();
Uri uri = new Uri("http://teluguone.com/movies/moviesXml.php", UriKind.Absolute);
downloader.DownloadStringCompleted += new DownloadStringCompletedEventHandler(MoviesDownloaded);
downloader.DownloadStringAsync(uri);
}
void MoviesDownloaded(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Result == null || e.Error != null)
{
MessageBox.Show("There was an error downloading the XML-file!");
}
else
{
// Deserialize if download succeeds
XmlSerializer serializer = new XmlSerializer(typeof(Movies));
XDocument document = XDocument.Parse(e.Result);
Movies movies = (Movies)serializer.Deserialize(document.CreateReader());
moviesList.ItemsSource = movies.Collection;
}
}
}
}
Movie.cs:
public class Movie
{
[XmlElement("MovieName")]
public string MovieName { get; set; }
[XmlElement("MovieActor")]
public string MovieActor { get; set; }
[XmlElement("MovieActress")]
public string MovieActress { get; set; }
[XmlElement("MovieDirector")]
public string MovieDirector { get; set; }
[XmlElement("MovieImage")]
public string MovieImage { get; set; }
}
Movies.cs:
[XmlRoot("Movies")]
public class Movies
{
[XmlArray("movies")]
[XmlArrayItem("movie")]
public ObservableCollection<Movie> Collection { get; set; }
}
ManyThanks in Advance
First understand basic XML structure and write properties based on that. Its very long to describe here, hence I have uploaded my sample project "XML deserialization into C# objects" in GitHub and refer for more info.
Step 1: Write properties based on XML Structure, which your retrieve from Source.
Step 2: Download the XML string using WebClient or Http class.
Step 3: Deserialize those strings using XMLSerializer and bind it to objects.
Step 4: Get respective terms using providing search options from objects.
You can refer my GitHub Project here.
I am working on a Windows Phone 8 app and I have some data that I need to work with. I am reading the data from a website and I have basically 4 pieces of information coming in from the website. I need to be able to do some parsing and filtering on this data before displaying it so I would like to add it to some sort of array/collection/dictionary.
Before with a C# WinForms app, I would just create a DataTable in memory that had 4 columns and just add a row for each entry. I don't think I can use DataTables with Windows Phone so I am wondering what other ways I can store this information in memory. The app doesn't store any information so I have no real need to use IsolatedStorage for any of this.
Is there some sort of Dictionary/Collection that I can use that will do something similar to a DataTable? In that it will let me store 4 pieces of information per entry?
Thanks!
If you need something simple, you could create a class which will hold the data:
public class DataFromWebSite
{
public string Name { get; set; }
public int Age { get; set; }
public string Country { get; set; }
public string Telephone { get; set; }
}
In the example above, I've used a strongly-typed class where each property could hold an exact value/data-type. So, Age is an integer rather than being a string for example.
Then, I'd create a collection object to hold the values:
var list = new ObservableCollection<DataFromWebSite>();
// add items to the list...
list.Add(new DataFromWebSite
{
Name = "John",
Age = 42,
Country = "Antartica",
Telephone = "941223445"
});
list.Add(new DataFromWebSite
{
Name = "Carrol",
Age = 24,
Country = "Atlantis",
Telephone = "100223445"
});
ObservableCollections work well with UIs where the list may change over time. For example, a ListBox bound to an ObservableCollection will show changes to the list when items are added or removed (but it won't show changes to the properties such as Age without implementing INotifyPropertyChange).
Then, using some of the LINQ extensions, you can do simple/readable queries:
var greaterThan30 = list.Where(d => d.Age > 30);
foreach (var item in greaterThan30)
{
Debug.WriteLine("{0}-{1}", item.Name, item.Age);
}
The above would produce a list that has only objects where the Age property is greater than 30.
Or you could use LINQ:
var greaterThan30 = from d in list
where d.Age > 30
select d;
If one of the values being returned was unique (for example an ID):
public class DataFromWebSite
{
public string Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Telephone { get; set; }
}
You could store the list as a Dictionary<string, DataFromWebSite>:
var keyed = new Dictionary<string, DataFromWebSite>();
var d = new DataFromWebSite
{
Id = "ABC123-A";
Name = "John",
Age = 42,
Telephone = "941223445"
};
keyed.Add(d.Id, d);
// to get a value back:
if (keyed.ContainsKey("ABC123-A")) {
var d2 = keyed["ABC123-A"];
}
And, if you wanted to get the list from the Dictionary:
var list = keyed.Values.ToArray();
This is a bit of an open-ended questions for XAML (as you're really asking how use collection controls and how to do binding).
Basically, any collection would do, then use a control to bind to the various properties of elements in that collection. You could use a ListBox control, bind it to the collection, then declare an ItemTemplate within it to bind to the various properties within the elements in the collection.
<ListBox ItemsSource="{Binding Customers}" Margin="0,5,0,10">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<TextBlock Padding="3,0,3,0"
Text="{Binding FirstName}" FontSize="{StaticResource PhoneFontSizeSmall}"/>
<TextBlock Text="{Binding LastName}" FontSize="{StaticResource PhoneFontSizeSmall}"/>
<TextBlock Text=", " FontSize="{StaticResource PhoneFontSizeSmall}"/>
<TextBlock Text="{Binding Address}" FontSize="{StaticResource PhoneFontSizeSmall}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And you'd set the DataContext of the page (or name the ListBox and set its DataContext) to an object that has property named Customers which is the collection you want to view in a list box e.g.:
public MyPage()
{
//...
DataContext = this;
}
public List<Customer> Customers {get;set;}
I've ignored INotifyPropertyChanged, I'll leave that as an exercise to the reader.