Having trouble adding items to a WPF ListView - c#

I've got a simple application connected to a SQL Server database, and I'm trying to pull data from the server to add it to a WPF ListView. I've done what I thought was the hard part of that, and successfully pulled the data - confirmed that already. But when I try to add it to my existing listview, the listview remains blank. Here's the code I've got to add it.
GetEmployees uses a middle tier class to connect to the database and retrieve the properties of an Employee. It successfully creates all of these variables and assigns them values from the database. The bottom line - lvwEmpSearch.Items.Add(emp); is what does not work.
Edit: Code is adjusted.
Code for adding to the listview:
public ObservableCollection<EmployeeViewModel> Employees { get; set; } = new ObservableCollection<EmployeeViewModel>();
private void btnFindAllEmployees_Click(object sender, RoutedEventArgs e)
{
List<Employee> empList = GetEmployees();
foreach (Employee emp in empList)
{
var model = new EmployeeViewModel
{
empID = emp._empID,
Name = emp._fName + " " + emp._lName,
Address = emp._address + ", " + emp._city + ", " + emp._state + " " + emp._zip,
HireDate = emp._doH,
Phone = emp._phone,
PayRate = emp._payRate,
Email = emp._email,
};
Employees.Add(model);
}
}
EmployeeViewModel class:
public class EmployeeViewModel
{
public int empID { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public DateTime HireDate { get; set; }
public string Phone { get; set; }
public decimal PayRate { get; set; }
public string Email { get; set; }
}

Create a ViewModel to hold the item information you want displayed.
Based on column binding you are looking for something like
public class EmployeeViewModel {
public int ID { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public DateTime HireDate { get; set; }
public string Phone { get; set; }
public decimal PayRate { get; set; }
public string Email { get; set; }
}
Create an observable collection to hold the items
public ObservableCollection<EmployeeViewModel> Employees { get; set; } = new ObservableCollection<EmployeeViewModel>();
and to allow the view to bind to the list
<ListView x:Name="lvwEmpSearch" ItemsSource="{Binding Employees}" >
<!-- ...removed for brevity -->
</ListView>
Now all that is needed is to populate the collection within the main ViewModel
List<Employee> empList = GetEmployees();
foreach (Employee emp in empList) {
var model = new EmployeeViewModel {
ID = emp._empID,
Name = emp._fName + " " + emp._lName,
Address = emp._address + ", " + emp._city + ", " + emp._state + " " + emp._zip,
HireDate = emp._doH,
Phone = emp._phone,
PayRate = emp._payRate,
Email = emp._email,
};
Employees.Add(model);
}
You seem to be doing everything in the code behind so you would need to bind the view. The initial assumption was that you were following the MVVM pattern.
//CTOR
public EmployeesView() {
this.InitializeComponents();
this.Employees = new ObservableCollection<EmployeeViewModel>();
//Bind the view so that
this.DataContext = this;
}
public ObservableCollection<EmployeeViewModel> Employees { get; private set; }
private void btnFindAllEmployees_Click(object sender, RoutedEventArgs e) {
//...code removed for brevity
}

Try doing the following:
In your view:
<ListView ItemsSource="{Binding Employees}">
<ListView.View>
<GridView>
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}"></GridViewColumn>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
In your code behind of the view:
public MainWindow() {
InitializeComponent();
DataContext = new MainViewModel();
}
The MainViewModel code:
public class MainViewModel : INotifyPropertyChanged {
public MainViewModel() {
FEmployees = new ObservableCollection<Employee>();
FEmployees.Add(new Employee {ID = 1,Name="Jordy van Eijk"});
FEmployees.Add(new Employee {ID = 2,Name="John Doe"});
FEmployees.Add(new Employee {ID = 3,Name="Jane Doe"});
}
private ObservableCollection<Employee> FEmployees;
public ObservableCollection<Employee> Employees {
get { return FEmployees; }
set {
FEmployees = value;
OnPropertyChanged(nameof(Employees));
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string APropertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(APropertyName));
}
}
public class Employee {
public int ID { get; set; }
public string Name { get; set; }
}

Youshould be able to create an ObservableCollection containing your employees and bind them to the ItemsSource.
private ObservableCollection<Employee> _employees = new ObservableCollection<Employee>();
public ObservableCollection<Employee> Employees
{
get { return _employees; }
protected set
{
if (_employees == value)
return;
_employees = value;
OnPropertyChanged("Employees");
}
}
And bind it like this:
<ListView ItemsSource="{Binding Employees}"/>
And in code you could fill it with:
Employees.AddRange(GetEmployees());

Related

UWP C# How to append .json file and to identify them in groups

I am trying to create a usercontrol to display groups of items from json file on windows iot core.
I have a "Create Group" button. Once pressed, it will create 64 usercontrols with respective details and display in a scrollviewer. susequently i can edit individual items in any of the 64 usercontrol then save the json file.
I have my class for the usercontrol as follow;
I have having an issue on how to create different groups of the 64 items and append all in a same json file and to subsequently display them from selection of the mentioned different groups.
Please help thanks.
Group Class
[DataContract]
public class DecoderGroup
{
[DataMember]
public int groupID{ get; set; }
[DataMember]
public string groupName{ get; set; }
[DataMember]
public int cardAddress { get; set; }
[DataMember]
public bool enabled { get; set; }
[DataMember]
public int z1label { get; set; }
[DataMember]
public int z2label { get; set; }
[DataMember]
public int z3label { get; set; }
[DataMember]
public int z4label { get; set; }
[DataMember]
public bool zone1 { get; set; }
[DataMember]
public bool zone2 { get; set; }
[DataMember]
public bool zone3 { get; set; }
[DataMember]
public bool zone4 { get; set; }
[DataMember]
public List<byte> txData { get; set; }
public DecoderGroup(int id, int address, int z1, int z2, int z3, int z4)
{
groupName = "Group";
zone1 = false;
zone2 = false;
zone3 = false;
zone4 = false;
z1label = z1;
z2label = z2;
z3label = z3;
z4label = z4;
}
}
MainPage.xaml.cs
private void AddGroup_Click(object sender, RoutedEventArgs e)
{
ZonesList_Panel.Children.Clear();
int groupid = 1;
int cardadr;
for (cardadr = 1; cardadr <= MAXCARDS; cardadr++)
{
var z4 = (4 * cardadr);
var z3 = (4 * cardadr) - 1;
var z2 = (4 * cardadr) - 2;
var z1 = (4 * cardadr) - 3;
DecoderGroupUserControl decoderGroupControl = new DecoderGroupUserControl(this, new DecoderGroup(groupid, cardadr, z1, z2, z3, z4));
ZonesList_Panel.Children.Add(decoderGroupControl);
}
}
private async void SaveGroup_Click(object sender, RoutedEventArgs e)
{
await saveGroupsToJSON(getGroups());
}
public async Task saveGroupsToJSON(List<DecoderGroup> groups)
{
var serializer = new DataContractJsonSerializer(typeof(List<DecoderGroup>));
using (var stream = await ApplicationData.Current.LocalFolder.OpenStreamForWriteAsync(DECODERGROUPS_FILE, CreationCollisionOption.OpenIfExists))
{
serializer.WriteObject(stream, groups);
}
}
public List<DecoderGroup> getGroups()
{
List<DecoderGroup> ret = new List<DecoderGroup>();
foreach (DecoderGroupUserControl u in ZonesList_Panel.Children)
{
//add condition for group ID
ret.Add(u.decoderGroup);
}
return ret;
}
UserControl
public DecoderGroupUserControl(MainPage page, DecoderGroup group)
{
this.InitializeComponent();
mainPage = page;
this.decoderGroup = group;
Z1Name.Text = group.z1label.ToString();
Z2Name.Text = group.z2label.ToString();
Z3Name.Text = group.z3label.ToString();
Z4Name.Text = group.z4label.ToString();
}
It is recommended to use UserControl as the DateTemplate of ListView so that you don’t need to create multiple usercontrol and add them to the page. Then you could read json file and convert json objects to a collection, and you could use the collection as the Itemsource of ListView.
By implementing the INotifyPropertyChanged interface, TwoWay data binding could reflect the UI changes to the collection. Finally, you could write the changed collection to the json file.
Note that you need to download the Newtonsoft.Json to parse the json object via Manage NuGet Packages. Please refer to the following code.
MyUserControl1.xaml:
<UserControl
..>
<Grid>
<!--customize the usercontrol style-->
<StackPanel>
<StackPanel Orientation="Horizontal" >
<TextBox Margin="0,0,20,0" Text="{Binding Name,Mode=TwoWay}" BorderThickness="0"/>
<TextBox Text="{Binding Job,Mode=TwoWay}" BorderThickness="0"/>
</StackPanel>
<TextBox Text="{Binding Age,Mode=TwoWay}" BorderThickness="0"/>
</StackPanel>
</Grid>
</UserControl>
MainPage.xaml:
<Page..>
<Grid>
<StackPanel>
<ListView ItemsSource="{x:Bind Results,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Person">
<local:MyUserControl1>
</local:MyUserControl1>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button x:Name="SaveButton" Content="Save" Click="SaveButton_Click"/>
</StackPanel>
</Grid>
</Page>
MainPage.xaml.cs:
namespace WriteJson
{
public sealed partial class MainPage : Page
{
public ObservableCollection<Person> Persons { get; set; }
public ObservableCollection<Person> Results { get; set; }
public string path;
public MainPage()
{
this.InitializeComponent();
CreateJsonFile();
path = ApplicationData.Current.LocalFolder.Path + "\\info.json";
Results = JsonConvert.DeserializeObject<ObservableCollection<Person>>(File.ReadAllText(path));
Debug.WriteLine("bind successfully");
}
public async void CreateJsonFile()
{
//check if info.json exists, if it doesn't exist, create it
StorageFolder folder = ApplicationData.Current.LocalFolder;
StorageFile file;
try
{
file = await folder.GetFileAsync("info.json");
}
catch
{
await folder.CreateFileAsync("info.json");
Persons = new ObservableCollection<Person>()
{
new Person(){Name="tom",Job="teacher",Age=24},
new Person(){Name="lily",Job="nurse",Age=20},
new Person(){Name="ming",Job="student",Age=26},
new Person(){Name="kiki",Job="lawyer",Age=28},
new Person(){Name="jack",Job="worker",Age=21},
};
path = ApplicationData.Current.LocalFolder.Path + "\\info.json";
File.WriteAllText(path, JsonConvert.SerializeObject(Persons));
Debug.WriteLine("create a json file successfully");
}
}
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
File.WriteAllText(path, JsonConvert.SerializeObject(Results));
Debug.WriteLine("save successfully");
}
}
public class Person:INotifyPropertyChanged
{
private string name;
private string job;
private int age;
public string Name
{
get { return name; }
set
{
name = value;
RaisePropertyChanged("Name");
}
}
public string Job
{
get { return job; }
set
{
job = value;
RaisePropertyChanged("Job");
}
}
public int Age
{
get { return age; }
set
{
age = value;
RaisePropertyChanged("Age");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyname=null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
}
}

Changes in SelectedItem of ComboBox alter ItemsSource

I have a ComboBox and a DataGrid in my XAML:
<ComboBox SelectedItem="{Binding SelectedFamily}" ItemsSource="{Binding FamilyList}" IsSynchronizedWithCurrentItem="True"/>
<DataGrid ItemsSource="{Binding FamilyInfoGrid}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="Age" Binding="{Binding Age}" />
</DataGrid.Columns>
</DataGrid>
This is my viewmodel:
public class MainWindowViewModel : INotifyPropertyChanged
{
private ReadOnlyObservableCollection<Family> _familyList;
public ReadOnlyObservableCollection<Family> FamilyList
{
get { return _familyList; }
}
private Family _selectedFamily;
public Family SelectedFamily
{
get
{
return _selectedFamily;
}
set
{
_familyInfoGrid.Clear();
_familyInfoGrid.Add(value.Kid);
_familyInfoGrid.Add(value.Parent2);
_familyInfoGrid.Add(value.Parent1);
RaisePropertyChangedEvent();
}
}
private ObservableCollection<Person> _familyInfoGrid = new ObservableCollection<Person>();
public ObservableCollection<Person> FamilyInfoGrid
{
get { return _familyInfoGrid; }
set { _familyInfoGrid = value; RaisePropertyChangedEvent(); }
}
public MainWindowViewModel()
{
var fam1 = new Family("Smith", new Person("Jim", 31), new Person("Eve", 29), new Person("Tom", 2));
var fam2 = new Family("Miller", new Person("Joe", 35), new Person("Sue", 33), new Person("Kim", 8));
_familyList = new ReadOnlyObservableCollection<Family>(new ObservableCollection<Family>() { fam1, fam2 } );
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
which means the DataGrid always gets updated to the currently selected item of the combobox - as it should.
This my model:
public class Family
{
public string Name { get; set; }
public Person Kid { get; set; }
public Person Parent1 { get; set; }
public Person Parent2 { get; set; }
public Family(string name, Person parent1, Person parent2, Person kid)
{
Name = name; Parent1 = parent1; Parent2 = parent2; Kid = kid;
}
public override string ToString()
{
return Name;
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name; Age = age;
}
}
The problem is that changes that are made within the DataGrid are stored in the _familyList, but I want the _familyList to be immutable and only be able to edit (and use) the current fields of the DataGrid temporarily (so Mode=OneWay is not an option either).
Thanks to #mm8 I managed to find a solution. The currently selected item of the combobox has to be a "deep copied":
public Family SelectedFamily
{
set
{
var fam = new Family(
new string(value.Name.ToCharArray()),
new Person(value.Parent1.Name, value.Parent1.Age),
new Person(value.Parent2.Name, value.Parent2.Age),
new Person(value.Kid.Name, value.Kid.Age));
_familyInfoGrid.Clear();
_familyInfoGrid.Add(fam.Kid);
_familyInfoGrid.Add(fam.Parent2);
_familyInfoGrid.Add(fam.Parent1);
RaisePropertyChangedEvent();
}
}

WPF ListView Items Add executing but not showing

I am trying to populate my List View which is basically a comparison between 2 folders.
I am using WPF List View :
XAML :
<ListView x:Name="listView" Margin="0,39,0,0">
<ListView.View>
<GridView x:Name="gridView"/>
</ListView.View>
</ListView>
MainWindow :
private void compare_Click(object sender, RoutedEventArgs e)
{
.
.
.
ListHelper listHelper = new ListHelper(listView, gridView);
CompareResults = results.CoalescedResults();
listHelper.AddItems(CompareResults);
}
ListHelper Class
class ListHelper
{
public ListView listView { get; set; }
public GridView gridView { get; set; }
public ListHelper (ListView list, GridView grid)
{
this.listView = list;
this.gridView = grid;
listView.View = gridView;
InitializeList();
}
public void InitializeList()
{
listView.Items.Clear();
// Add columns
addColumns("File/Folder Name");
addColumns("Left Folder");
addColumns("Right Folder");
addColumns("Match");
}
public void addColumns(string colName)
{
gridView.Columns.Add(new GridViewColumn
{
Header = colName,
DisplayMemberBinding = new Binding(colName),
Width = 30
});
}
public void AddItems(List<CompareResult> compareResults)
{
foreach (var item in compareResults)
{
ListDataRow row = new ListDataRow()
{
TypeName = item.GetFileOrFolderName(),
LeftFolder = item.LeftFilePath != string.Empty ? Path.GetDirectoryName(item.LeftFilePath) : string.Empty,
RightFolder = item.RightFilePath != string.Empty ? Path.GetDirectoryName(item.RightFilePath) : string.Empty,
MatchStatus = "MATCH STATUS TEMP"
};
listView.Items.Add(row);
}
}
ListDataRow
public class ListDataRow
{
public string TypeName { get; set; }
public string LeftFolder { get; set; }
public string RightFolder { get; set; }
public string MatchStatus { get; set; }
}
Now on adding breakpoints, I can see that valid data in being inserted in listView Add items call..
But on execution, only columns are visible, no data items.
Thanks everyone,
I figured out the error.
Please match the Names of Columns with the object (ListDataRow in above example)

Passing json object data from a ListView SelectedItem

I need some help with passing the ListView Tapped Id (which I get from a json).
I populate the listView with an API call to a server:
private async void searchButton_Click(object sender, RoutedEventArgs e)
{
var textFrom = odTextBox.Text;
var textTo = doTextBox.Text;
var searchResult = await PrevoziApi.SearchRidesAsync(textFrom, textTo, datePicker.Date.UtcDateTime);
var array = searchResult.CarshareList
.OrderBy(cs => cs.Time)
.Select(cs => cs.Contact + " " + cs.Time)
.ToArray();
listView.ItemsSource = array;
}
Now, when I click on an item of listView, I want to navigate to another page(CarShareDetailedPage) and make another call to the API, to get more detailed data about that item. So I need to pass the selectedItem id from one page to other. How do I do that ?
I'm navigating to another page like this:
private void listView_Tapped(object sender, TappedRoutedEventArgs e)
{
Frame.Navigate(typeof(CarShareDetailedPage), listView.SelectedIndex);
}
The OnNagiatedMethod on that page is:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var value = e.ToString();
carShareTextBox.Text = value;
}
And my json class is:
public class CarshareList
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("from_id")]
public string FromId { get; set; }
[JsonProperty("from_country")]
public string FromCountry { get; set; }
[JsonProperty("from_country_name")]
public string FromCountryName { get; set; }
[JsonProperty("to_id")]
public string ToId { get; set; }
[JsonProperty("to")]
public string To { get; set; }
[JsonProperty("to_country")]
public string ToCountry { get; set; }
[JsonProperty("to_country_name")]
public string ToCountryName { get; set; }
[JsonProperty("time")]
public string Time { get; set; }
[JsonProperty("date_iso8601")]
public DateTime DateIso8601 { get; set; }
[JsonProperty("added")]
public DateTime Added { get; set; }
[JsonProperty("price")]
public double? Price { get; set; }
[JsonProperty("num_people")]
public double NumPeople { get; set; }
[JsonProperty("author")]
public string Author { get; set; }
[JsonProperty("is_author")]
public string IsAuthor { get; set; }
[JsonProperty("comment")]
public string Comment { get; set; }
[JsonProperty("contact")]
public string Contact { get; set; }
[JsonProperty("date")]
public string Date { get; set; }
[JsonProperty("full")]
public string Full { get; set; }
[JsonProperty("insured")]
public string Insured { get; set; }
[JsonProperty("share_type")]
public string ShareType { get; set; }
[JsonProperty("confirmed_contact")]
public string ConfirmedContact { get; set; }
[JsonProperty("bookmark")]
public object Bookmark { get; set; }
[JsonProperty("from")]
public string From { get; set; }
}
public class CarshareResponse
{
[JsonProperty("search_type")]
public string SearchType { get; set; }
[JsonProperty("carshare_list")]
public IList<CarshareList> CarshareList { get; set; }
}
Let me say this is the first time ever I'm doing any work with Apis and json.
Thanks for your help!
EDIT: I added the code for the API below, so this now should be all the code I have.
public class PrevoziApi
{ public static async Task<CarshareResponse> SearchRidesAsync(
string fromCity,
string toCity,
DateTime date,
string type = "shares",
CancellationToken token = default(CancellationToken))
{
using (var client = new RestClient("https://prevoz.org/api/"))
{
var request = new RestRequest("search/" + type + "/", HttpMethod.Get);
request.AddQueryParameter("f", fromCity);
request.AddQueryParameter("fc", "SI");
request.AddQueryParameter("t", toCity);
request.AddQueryParameter("tc", "SI");
request.AddQueryParameter("d", date.ToString("yyyy-MM-dd"));
request.AddQueryParameter("exact", "true");
return
(await client.Execute<CarshareResponse>(request, token)).Data;
}
}
}
So with this, you are ordering by the time but displaying a string only that says "[Contact] [Time]". This in-and-of-itself does not hold any relation to the JSON that was returned from your search method. What you'll want to do is instead of making it an array, instead making a List<> object that can store some additional "background" data about that request to send off.
This will require a bit more effort though on your end. You will want to create a class
public class CarItemView {
public string DisplayText {get; set;}
public int ID {get; set;}
}
and fill it with whatever data you want to pass along. Then in your filtering you would do:
List<CarItemView> array = searchResult.CarshareList
.OrderBy(cs => cs.Time)
.Select(cs => new { DisplayText = cs.Contact + " " + cs.Time, ID = cs.Id}).ToList();
You will then, in your XAML, have to add a template to your listview for display. (Note, this is a real rough outline for a XAML Template)
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding DisplayText}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
NOW when you get your selected item changed event fired, you can handle it and get the ID.
private void listView_Tapped(object sender, TappedRoutedEventArgs e)
{
var obj = (CarItemView) listView.SelectedItem; // convert item to our new class
Frame.Navigate(typeof(CarShareDetailedPage), obj.Id.ToString()); // send ID as string
}
Then for the receiving page:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var value = e.ToString();
carShareTextBox.Text = value; // will show the ID number
var caritemret = /* write a new restful function to return based on ID */
}
UPDATE: This answer was updated from original to reflect the use of an array instead of a list<> object
I hope this helps!
This works:
private async void searchButton_Click(object sender, RoutedEventArgs e)
{
var textFrom = odTextBox.Text;
var textTo = doTextBox.Text;
var searchResult = await PrevoziApi.SearchRidesAsync(textFrom, textTo, datePicker.Date.UtcDateTime);
List<CarItemView> array = searchResult.CarshareList
.OrderBy(cs => cs.Time)
.Select(cs => new CarItemView { DisplayText = cs.Contact + " " + cs.Time, Id = cs.Id })
.ToList();
listView.ItemsSource = array;
}
private void listView_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
var obj = (CarItemView)listView.SelectedItem; // convert item to our new class
Frame.Navigate(typeof(CarShareDetailedPage), obj.Id); // send ID as string
}
And on navigated to method on the destination page:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var value = e.Parameter.ToString();
carShareTextBox.Text = value; // will show the ID number
/* write a new restful function to return based on ID */
}
Thanks #daniel, it was mostly as you suggested, with a few errors, but with the help of some guys at the c# chat channel I managed. Thanks to all.

WPF Binding GridView to Element in collection

I'm creating a form that will allows user to add filters to data for processing.
I have setup:
public Class RuleGroup
{
public ObservableCollection<Rule> Rules {get; set;}
...
}
public Class Rule
{
public ObservableCollection<String> Fields {get; set;}
public ObservableCollection<Rule> Rules {get; set;}
...
}
public class Criteria
{
public int ItemId{ get; set;}
public string Field{ get; set;}
public OperationType Operation{ get; set;}
public string Value {get; set;}
public string Value2 {get; set;}
}
So a Rule has a List of Criteria that must be matched if the rule is to be applied. Each Criteria in a Rule must specify a value for every field selected. The Amount of fields may vary from One RuleGroup to the next.
I am trying to set up a form that is user friendly when creating multiple Rules. I was thinking of having a GridView on the form that is some how bound to this class layout.
Rule = Row
Criteria = Column
Currently I have function that generates a DataTable based on the Rules/Criteria as the user move from one RuleGroup to the next, but I think there my be an nicer solution to this
Any ideas or help would be much appreciated
Thanks
Right Think I have got it.
Needed to change my Classes around a bit to get the correct groups / hierarchy. I have then been able to bind the column using the items index in the collection.
This has given me the outcome I wanted, Though there is a minor issue where I would like to be able to access the index using the string Name rather then the position. I am currently having to make sure that the "Criterion" are in the correct order when accessing the values.
Here is a link to the Source code
Rule Group
public class RuleGroup
{
public String Description { get; set; }
public ObservableCollection<Rule> Rules { get; set; }
public RuleGroup()
{
Rules = new ObservableCollection<Rule>();
}
}
Rule
public class Rule
{
public Rule()
{
Criteria = new ObservableCollection<Criteria>();
}
public String Description { get; set; }
public ObservableCollection<Criteria> Criteria { get; set; }
readonly ObservableCollection<RuleField> _Fields = new ObservableCollection<RuleField>();
public IEnumerable<RuleField> Fields
{
get
{
return _Fields;
}
}
public void AddField(string name, string header)
{
if (_Fields.FirstOrDefault(i => i.Name == name) == null)
{
RuleField field = new RuleField(_Fields.Count)
{
Name = name,
Header = header
};
_Fields.Add(field);
AddFieldToCriteria(field);
}
}
void AddFieldToCriteria(RuleField field)
{
foreach (Criteria c in Criteria)
{
if (c.Values.FirstOrDefault(i => i.Field == field) == null)
c.Values.Add(new Criterion() { Field = field, Operation = 1 });
}
}
}
Criteria
public class Criteria
{
public Criteria()
{
Values = new ObservableCollection<Criterion>();
}
public ObservableCollection<Criterion> Values { get; set; }
public Criterion this[int index]
{
get
{
return Values.OrderBy(i=>i.Field.Position).ElementAt(index);
}
set
{
Criterion c = Values.OrderBy(i => i.Field.Position).ElementAt(index);
c= value;
}
}
}
Criterion
public class Criterion
{
public RuleField Field { get; set; }
public int Operation { get; set; }
public object Value { get; set; }
public object Value2 { get; set; }
}
RuleField
public class RuleField
{
public string Name { get; set; }
public string Header { get; set; }
int _Position = 0;
public int Position
{
get
{
return _Position;
}
}
public RuleField(int position)
{
_Position = position;
}
}
View Model
public delegate void UpdateColumnsEventHandler(object sender, UpdateColumnsEventArgs e);
public class UpdateColumnsEventArgs
{
public IEnumerable<RuleField> Columns { get; set; }
public UpdateColumnsEventArgs()
{
Columns = new List<RuleField>();
}
public UpdateColumnsEventArgs(IEnumerable<RuleField> columns)
{
Columns = columns;
}
}
public class MainWindowViewModel
{
public event UpdateColumnsEventHandler UpdateColumns;
public ObservableCollection<RuleGroup> RuleGroups { get; set; }
RuleGroup _SelectedRuleGroup = null;
public RuleGroup SelectedRuleGroup
{
get
{
return _SelectedRuleGroup;
}
set
{
if (_SelectedRuleGroup == value)
return;
_SelectedRuleGroup = value;
}
}
public Rule _SelectedRule = null;
public Rule SelectedRule
{
get
{
return _SelectedRule;
}
set
{
if (_SelectedRule == value)
return;
_SelectedRule = value;
if (UpdateColumns != null)
UpdateColumns(this, new UpdateColumnsEventArgs(_SelectedRule.Fields));
}
}
public MainWindowViewModel()
{
RuleGroups = new ObservableCollection<RuleGroup>();
RuleGroup rg = new RuleGroup();
rg.Description = "Rule Group A";
Rule r = new Rule();
r.Description = "Rule 1";
Random random = new Random();
int range = 10000;
for (int x = 0; x < 2000; x++)
{
Criteria c = new Criteria();
c.Values.Add(new Criterion()
{
Field = new RuleField(0)
{
Name = "A",
Header = "A Column"
},
Operation = 1,
Value = "X"
});
c.Values.Add(new Criterion()
{
Field = new RuleField(0)
{
Name = "B",
Header = "B Column"
},
Operation = 1,
Value = x % 10
});
r.Criteria.Add(c);
}
#region Fields
r.AddField("A", "A Column");
r.AddField("B", "B Column");
r.AddField("C", "C Column");
#endregion
rg.Rules.Add(r);
r = new Rule();
r.Description = "Rule 2";
for (int x = 0; x < 1750; x++)
{
r.Criteria.Add(new Criteria());
}
#region Fields
r.AddField("A", "A Column");
r.AddField("B", "B Column");
#endregion
rg.Rules.Add(r);
RuleGroups.Add(rg);
}
}
WPF Window
<Window x:Class="RuleMappingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:RuleMappingTest"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainWindowViewModel UpdateColumns="UpdateGridColumns"/>
</Window.DataContext>
<Grid Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ListBox Grid.Row="0" ItemsSource="{Binding RuleGroups}" SelectedItem="{Binding SelectedRuleGroup}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Grid.Row="1" ItemsSource="{Binding SelectedRuleGroup.Rules}" SelectedItem="{Binding SelectedRule}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<DataGrid x:Name="CriteriaGrid" Grid.Row="2" ItemsSource="{Binding SelectedRule.Criteria}" AutoGenerateColumns="False" >
</DataGrid>
</Grid>
</Window>
WPF Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public void UpdateGridColumns(object sender, UpdateColumnsEventArgs e)
{
CriteriaGrid.Columns.Clear();
foreach (RuleField rf in e.Columns)
{
DataGridTextColumn c = new DataGridTextColumn();
c.Header = rf.Header;
Binding b = new Binding(String.Format("[{0}].Value", rf.Position));
CriteriaGrid.Columns.Add(c);
c.Binding = b;
}
}
}

Categories