How to display JSON in WPF TreeView - json

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"};
}

Related

Trying to separate 2 items in a column for listview wpf

I am quite a newbie with wpf...any help will be appreciated.
I started a small project with a listview that displays content from MySQL. So far I had no problems except a column that has 2 items in it. I need to separate each item in its own column.
It was easy to do with date and time but this one is beyond my skills.
The display of the listview is like that (I can't post images yet):
Date |Time |CallerID |From|To |Duration
10 June 2015 |22:45|"alex" <210555555>|101 |201|234
The CallerID column contains the two values with distinct "" and <>. I need to separate as I did with Date and Time. Thanks for any help.
<ListView x:Name="Datalist" Grid.Column="1" Grid.Row="4"
ItemsSource="{Binding Path=DS}" Background="White" Foreground="Black" FontSize="16" Grid.ColumnSpan="4" FontFamily="Segoe UI" Margin="1,0,8,0">
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn Header="Date" DisplayMemberBinding="{Binding Path=calldate,StringFormat={}{0:dd MMMM yyyy}}"/>
<GridViewColumn Header="Time" DisplayMemberBinding="{Binding Path=calldate,StringFormat={}{0:HH:mm:ss}}"/>
<GridViewColumn Header="CallerID" DisplayMemberBinding="{Binding Path=clid}"/>
<GridViewColumn Header="From" DisplayMemberBinding="{Binding Path=src}"/>
<GridViewColumn Header="To" DisplayMemberBinding="{Binding Path=dst}"/>
<GridViewColumn Header="Duration" DisplayMemberBinding="{Binding duration}" />
</GridView>
</ListView.View>
</ListView>
private void OnLoad(object sender, RoutedEventArgs e)
{
string cs = #"server=192.168.1.123;userid=alex;
password=r3s3ll3r;database=asteriskcdrdb";
MySqlConnection conn = null;
MySqlDataReader rdr = null;
try
{
conn = new MySqlConnection(cs);
conn.Open();
string stm = "(SELECT * FROM cdr ORDER BY uniqueid DESC LIMIT 1000)";
mySqlDataAdapter = new MySqlDataAdapter(stm, cs);
mySqlDataAdapter.Fill(DS);
Datalist.ItemsSource = DS.DefaultView;
}
catch (MySqlException ex)
{
MessageBox.Show("Error: {0}", ex.ToString());
}
finally
{
if (rdr != null)
{
rdr.Close();
}
if (conn != null)
{
conn.Close();
}
}
}
Consider moving towards a more complete view model approach. Instead of binding your ItemsSource to the DataTable.DefaultView directly, you could create a collection of view models that define the presentation logic for each entry. For example, you could define a view model class such as
public class PhoneCallViewModel
{
public Date CallDate { get; set; }
public string CallerId { get; set; }
// .... Expose properties for each column
}
This will open up a lot of options for dealing with your current problem. For example, how could we split up that caller ID string? You could create another view model, such as the following
public class CallerIDViewModel
{
public string CallerName { get; set; }
public string CallerNumber { get; set; }
}
Then the PhoneCallViewModel would expose a CallerIDViewModel property. In your OnLoad method, you would create a collection of these PhoneCallViewModel objects (preferably an ObservableCollection) and bind the ItemsSource to it. Then you could simply replace your existing caller id column in your ListView with two new columns. One bound to CallerID.CallerName, and the second bound to CallerID.CallerNumber (assuming the property on PhoneCallViewModel is named CallerID).
I've left out the details of splitting the original caller ID as well as instantiating the collection, as those are separate implementation problems.
You could always create a converter and pass your clid to it with a parameter saying if you want the name of the number and it would return you the right one.
It would look like:
<GridViewColumn Header="CallerID" DisplayMemberBinding="{Binding Path=clid, Converter={StaticResource GetCallerIdConverter}, ConverterParameter='Name'}"/>
and then the converter:
public class GetCallerIdConverter : IValueConverter {
public object Convert( object value, Type targetType, object parameter, CultureInfo culture ) {
string callerId = value.toString();
if (parameter == "Name")
{
//your regex to return what's in the double quotes ""
}
else
{
//your regex to return what's in the < >
}
}
Thank you all for the help, this is a great community!
So, i used a converter as maxime-tremblay-savard suggested.
class GetCalleridConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string callerId = (string)value;
Regex angleBrackets = new Regex("<[^(>)]*>");
Regex quotes = new Regex("\"([^\"]*)\"");
if (parameter.ToString() == "Number")
{
//your regex to return what's in the double quotes ""
Match angled = angleBrackets.Match(callerId);
return angled.ToString();
}
else
{
Match quot = quotes.Match(callerId);
return quot.ToString();
}
}
//And then i binded the item to xaml
.....
<Page.Resources>
<valueconverter:GetCalleridConverter x:Key="GetCallerIdConverter" />
</Page.Resources>
.......
<GridViewColumn Header="Caller Name" Width="auto" DisplayMemberBinding="{Binding Path=clid, Converter={StaticResource GetCallerIdConverter}, ConverterParameter='Name'}"/>
<GridViewColumn Header="Caller Number" Width="auto" DisplayMemberBinding="{Binding Path=clid, Converter={StaticResource GetCallerIdConverter}, ConverterParameter='Number'}"/>
//So, i got two columns with caller Name and Caller Number, only thing left to remove quotes and angle brackets....

binding not being done with json data

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.

Binding data from Linq Database using Observable Collection - WP8

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.

Win 8.1 SearchBox - binding suggestions

We are writing a Windows 8.1 Store App that uses the new SearchBox XAML control. It looks like the only way to get suggestions into the dropdown list as the user types is to use the SearchBoxSuggestionsRequestedEventArgs and get the SearchSuggestionCollection from the event then append the suggestions to that.
We're using Prism for WinRT and want to separate the SearchBox and it's events from the ViewModel that is getting the list of suggestion strings.
I can't find anyway of binding a list of strings to the SearchSuggestionCollection or any way of adding them programatically that doesn't involve using the event args, which is making out unit testing very complex.
Is there a way of binding/adding the suggestions that doesn't involve the event args?
Okay, so I got obsessed with this question, and here is a solution for when using the SearchBox. I've uploaded a full sample on MSDN and GitHub
In short, use the Behavior SDK and and the InvokeCommand, and then use a converter to grab whatever data you need by using the new attributes InputConvert and InputConverterParameter.
XAML:
<SearchBox SearchHistoryEnabled="False" x:Name="SearchBox" Width="500" Height="50">
<SearchBox.Resources>
<local:SearchArgsConverter x:Name="ArgsConverter"/>
</SearchBox.Resources>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SuggestionsRequested">
<core:InvokeCommandAction
Command="{Binding SuggestionRequest}"
InputConverter="{StaticResource ArgsConverter}"
InputConverterLanguage="en-US"
InputConverterParameter="{Binding ElementName=SearchBox, Path=SearchHistoryEnabled}"/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</SearchBox>
Converter:
public sealed class SearchArgsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var args = (SearchBoxSuggestionsRequestedEventArgs)value;
var displayHistory = (bool)parameter;
if (args == null) return value;
ISuggestionQuery item = new SuggestionQuery(args.Request, args.QueryText)
{
DisplayHistory = displayHistory
};
return item;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
}
Mainpade codebehind - of course you want this in a VM :)
public sealed partial class MainPage
{
public DelegateCommand<string> Search { get; set; }
public DelegateCommand<ISuggestionQuery> SuggestionRequest { get; set; }
public MainPage()
{
InitializeComponent();
Search = new DelegateCommand<string>(SearchedFor, o => true);
SuggestionRequest = new DelegateCommand<ISuggestionQuery>(SuggestionRequestFor, o => true);
DataContext = this;
}
private void SuggestionRequestFor(ISuggestionQuery query)
{
IEnumerable<string> filteredQuery = _data
.Where(suggestion => suggestion.StartsWith(query.QueryText,
StringComparison.CurrentCultureIgnoreCase));
query.Request.SearchSuggestionCollection.AppendQuerySuggestions(filteredQuery);
}
private readonly string[] _data = { "Banana", "Apple", "Meat", "Ham" };
private void SearchedFor(string queryText)
{
}
}
I wrote up a full walk through on my blog, but the above is all you really need :)

Binding a Dictionary to a WinRT ListBox

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.