I'm using the WPF toolkit datagrid and in the past have always created entities for the grid to bind to, so for example a Contact Entity with Name, Address etc.
On the current app I'm working on the user may select from 50 tables and individually select the fields from the tables to generate a view.
Clearly here having an Entity to bind to will not work as the binding source will be dynamic.
Question is what do I do?
Thanks
I just blogged about how to dynamically create columns for a DataGrid based on a reusable model.
The best solution is to use Anonymous Types it works perfectly, see the following proof of concept:
<Window x:Class="MyProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
Title="MainWindow"
Height="136" Width="525"
Loaded="OnWindowLoaded">
<DataGrid ItemsSource="{Binding}">
</DataGrid>
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
namespace MyProject {
public partial class MainWindow : Window
{
public class Employee
{
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public int Job { get; set; }
public string Address { get; set; }
}
private ObservableCollection<Employee> _empCollection;
public MainWindow()
{
InitializeComponent();
}
private void OnWindowLoaded(object sender, RoutedEventArgs e)
{
// Generate test data
_empCollection =
new ObservableCollection<Employee>
{
new Employee {Id = 234, Code = "E041", Name = "Employee1", Job = 1, Address = "..."},
new Employee {Id = 245, Code = "E701", Name = "Employee2", Job = 3, Address = "..."},
new Employee {Id = 728, Code = "E001", Name = "Employee3", Job = 9, Address = "..."},
new Employee {Id = 663, Code = "E051", Name = "Employee4", Job = 7, Address = "..."},
};
DataContext =
(from i in _empCollection
select new {i.Code, i.Name, i.Address}).ToList();
}
}
}
One approach would be to create a collection of objects, and give each object a custom TypeDescriptor.
When the grid is auto generating columns, it uses reflection over your class - e.g., Customer, and discovers its properties - e.g., FirstName, Balance, etc.
But that's not entirely true. WPF doesn't do the work itself - it asks a TypeDescriptor. And you can implement your own TypeDescriptor, so you can pretend to have properties that don't actually exist. Or in your case, pretend not to have properties that do exist.
You can leave binding source as it is, however you can filter DataGrid's columns based on user's preferences of what he/she needs to hide or see.
Related
I have a Custom control inheriting from Control class in my WinForm. My control contains multiple panels and other UIElements.
This is what my control is supposed to look like
There's a database panel,
database panel contains a single checkbox only.
and there's a Server panel,
server panel contains many database panels and a single label; the header label.
And finally there's the container panel that contains all my Server panels.
I found this Item Collection option for a User Control but I couldn't really understand the accepted answer on it. If someone could help explain it better that would be great.
Also, if someone could just put some links for creating advanced custom controls. I've been reading all day about it and I still can't make any sense of it all. Is there a step-by-step guide for advanced custom controls?
[Edit]
Basically what I need is to create a custom collection within my custom control. Currently my control is built as Winform Control Library which I build and then I use in my main program later.
So in my main program, I can just drag and drop the component on my form and use it.
By default, the custom control will load with one Server that contains one database.
What I want is to be able to add/remove other databases/servers to it if I need to, in my MAIN program
I'm having trouble explaining exactly what I need because I simply do not understand how the whole custom control/items collection thing works really, and i'm sorry for that. I would really appreciate some links that explains this stuff clearly
here's my code for this control:
This code only creates my default control, but I am UNABLE to add to it. The collection property appears in my property windows but when I add items to it and click okay nothing happens.
public class Database : System.Windows.Forms.Panel
{
public CheckBox _ckbDatabase;
public Database()
{
_ckbDatabase = new CheckBox();
this.BackColor = _pnlDatabaseBackColor;
this.Size = _pnlDatabaseSize;
this.AutoSize = false;
this.Height = 40;
this.Width = 200;
this.Location = _pnlDatabaseLocation;
_ckbDatabase.Top = 10;
_ckbDatabase.Left = 15;
_ckbDatabase.TextAlign = _ckbdbTextAlignment;
_ckbDatabase.Font = _ckbdbFont;
_ckbDatabase.ForeColor = Color.White;
this.Controls.Add(_ckbDatabase);
}
#Propterties
}
public class Server : System.Windows.Forms.Panel
{
private Label _lblserver;
private Database database;
public Server()
{
_lblserver = new Label();
database = new Database();
this.BackColor = _pnlServerBackColor;
this.Size = _pnlServerSize;
this.AutoSize = false;
_lblserver.Dock = _lblserverDock;
_lblserver.Font = _lblsrvFont;
_lblserver.BackColor = _lblServerBackColor;
_lblserver.AutoSize = false;
_lblserver.Text = SRV;
database.Top = 35;
database._ckbDatabase.Text = DB;
this.Controls.Add(_lblserver);
this.Controls.Add(database);
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public DatabaseCollection DatabaseCollection { get; set; }
#Propterties
}
public class ServersCollection : CollectionBase
{
public Server this[int index]
{
get { return (Server)List[index]; }
}
public void Add(Server server)
{
List.Add(server);
}
public void Remove(Server server)
{
List.Remove(server);
}
}
How about something simple like this:
public class Server {
public string Name { get; set; }
public List<Database> Databases { get; set; }
public Server() {
Databases = new List<Database>();
}
}
public class Database {
public string Name { get; set; }
public bool Enabled { get; set; }
}
Then you can just add it like this:
List<Server> servers = new List<Server>();
Server serverA = new Server { Name = "Server A" };
serverA.Databases.Add(new Database { Name = "Database 1", Enabled = true });
serverA.Databases.Add(new Database { Name = "Database 2", Enabled = false });
Server serverB = new Server { Name = "Server B" };
serverB.Databases.Add(new Database { Name = "Database 1", Enabled = false });
serverB.Databases.Add(new Database { Name = "Database 2", Enabled = false });
servers.Add(serverA);
servers.Add(serverB);
When you link to the Item Collection part it seemed like you wanted to be able to add servers and databases in design mode but then you mention you want to do it by code? If this is not what you want you need to give us more information.
Looks to me like you are mostly there. First off, here's a more complete collection class:
public class ServersCollection : IEnumerable<Server>
{
private List<Server> _servers = new List<Server>();
public Server this[int index]
{
get { return _servers[index]; }
}
public IEnumerator<Server> GetEnumerator()
{
foreach (var server in _servers)
yield return server;
}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
public void Add(Server server)
{
_servers.Add(server);
}
public void Remove(Server server)
{
//You might consider a deliberate loop to evaluate a proper match
//Don't forget to Dispose() it first!
_servers.Remove(server);
}
public void Clear()
{
for (Int32 i = _servers.Count - 1; i >= 0; i--)
_servers[i].Dispose();
_servers.Clear();
}
}
Add an instance of the ServersCollection class to the container control, the one at the top level that holds server panels:
private ServersCollection _servers = new ServersCollection();
public ServersCollection Servers { get { return _servers; } }
Use that as a way for it to add Server controls to its own collection of controls.
Do a similar thing with the DatabaseCollection in the Server class, again so that it can add Database panels to its controls collection.
Then, wherever you have an instance of a control, you will also have access to the collection of what it holds:
myControl.Servers
//-or-
myServer.Databases
...allowing you to add/remove, as such:
myControl.Servers.Add(new Server());
//-or-
myServer.Databases.Add(new Database());
Points of emphasis
Your classes are controls, but they also own other controls. Proper use of the Dispose pattern will be crucial or you'll have memory issues throughout.
I would remove these lines, they don't matter unless you intend to add servers/DBs at form design time (i.e. fixed entries or defaults):
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public DatabaseCollection DatabaseCollection { get; set; }
Finally, you could (should!) take that collection class further, with overloads for Add() and Remove() that do a better job of deciding when/how/what to do based on more than an instance, e.g. by name? You could also add another indexer to fetch by name, for instance, instead of just index (which you might not readily know).
This has me stumped - hopefully someone can point out an obvious error. I have a user control that I am adding to a grid in the MainView of my program. Main view is bound to MainViewModel and the usercontrol is bound to CardioVM.
I have used a test label to check that the routing of the user control is correct and all work ok. I have a class named Cardio which has a property of
List<string> exercises
I am trying to pass the strings in
Cardio.List<string> exercises
to a
List<string> CardioList
in my CardioVM. When debugging
List<string> CardioList
is getting populated with items from
Cardio.List<string> exercises
but my ComboBox is not displaying the items on screen. Here is xaml for my UserControl and :
<UserControl x:Class="CalendarTest.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
DataContext="{Binding CardioVM, Source={StaticResource Locator}}">
<Grid>
<ComboBox ItemsSource="{Binding CardioList, Mode=OneWay}" SelectedItem="{Binding SelectedCardio, Mode=TwoWay}" Height="50"></ComboBox>
</Grid>
and here is the code for my CardioVM:
public class CardioVM : ViewModelBase
{
public Cardio cardioItem { get; set; }
public CardioVM()
{
TestLabel = "Tester";
}
//Test Label for binding testing
private string testLabel;
public string TestLabel
{
get { return testLabel; }
set
{
testLabel = value;
RaisePropertyChanged("TestLabel");
}
}
public CardioVM(string Date, string File)
{
cardioItem = new Cardio(File, Date);
CardioList = new List<string>(cardioItem.exercises);
}
private List<string> cardioList;
public List<string> CardioList
{
get { return cardioList; }
set
{
cardioList = value;
RaisePropertyChanged("CardioList");
}
}
private string _selectedCardio;
public string SelectedCardio
{
get { return _selectedCardio; }
set
{
_selectedCardio = value;
RaisePropertyChanged("SelectedCardio");
}
}
}
}
Not sure where I am going wrong here but any pointers would be much appreciated.
Here is where I thought I was adding in the userControl to a content control bound proprty in my Main view model:
public void NewTemplateExecute()
{
TextHideTab = "Close";
NewTemplateType = ("New " + SelectedExercise + " Exercise Template");
//Set the message and lists based on the exercise selected plus adds the drop down control
switch (SelectedExercise)
{
case "Cardio":
///
//This is where I thought CardioVM was being added
///
NewTemplateText = "Please choose a cardio exercise from the drop down list to the left. You can then select the duration of the exercise and the intensity. To add another exercise please press the plus button in the right hand corner";
ExerciseDropDowns = new CardioVM(selectedDateLabel, #"Model\Repository\Local Data\CardioList.txt");
break;
case "Weights":
NewTemplateText = "Please select a exercise type. you can refine your exercises by body area. Then add the number of sets and the reps per set. Add as many exercises as you like - dont forget to set to total duration";
break;
case "HIIT":
NewTemplateText = "HIIT to add";
break;
}
Messenger.Default.Send("NewTemplate");
}
I had set the datacontext for CardioVM in my Mainwindow xaml as:
<DataTemplate DataType="{x:Type local:CardioVM}">
<view:UserControl1/>
</DataTemplate>
I presume I have made a mistake in the way that I have hooked up CaridoVM but couldn't seem to get it to databind unless I sent it through the VM locator
Thanks to nemesv - you were of course spot on. Removed the DataContext from my CardioVM and now just using DataTemplate set in Main view to bind the Cardio view to the ViewModel. I can now call a cardioVM with parameters from the Mainview and it populates my combobox as expected. Seems I nbeeded to touch up on some of the basics of MVVM
I had made a class in C# Windows form to represent my database.
It has a master/detail using List<>
A record with Employee profile with Trainings(master) and TrainingDetails(detail)
Now, how can I display this into 2 datagridview that whenever I select a "Training" from the first datagridview it will display the details on the 2nd datagridview.
Its easy to change the datasource of the 2nd datagridview whenever the user select a new item from the first datagridview. But im wondering how it is done professionally.
Also saving is a pain, Im thingking to iterate through the datarow and save it but It mean I have to know what are the data has been update, inserted and deleted.
Please help me. Im a newbie.
BindingSources take care of this for you.
For example say I have two classes, Teachers and Students:
public class Teacher
{
private List<Student> _students = new List<Student>();
public string Name { get; set; }
public string Class { get; set; }
public List<Student> Students { get { return _students; } }
}
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
}
You can then create a list of Teachers which represents the Master/Detail situation:
List<Teacher> teachers = new List<Teacher>();
Teacher t = new Teacher();
t.Name = "Mr. Smith";
t.Class = "A1";
teachers.Add(t);
Student s = new Student();
s.Name = "Jimmy Jones";
s.Age = 6;
t.Students.Add(s);
s = new Student();
s.Name = "Jane Doe";
s.Age = 5;
t.Students.Add(s);
t = new Teacher();
t.Name = "Ms. Allen";
t.Class = "B3";
teachers.Add(t);
s = new Student();
s.Name = "Sally Student";
s.Age = 7;
t.Students.Add(s);
On my form I have two DataGridViews, teachersDataGridView and studentsDataGridView and two binding sources teachersBindingSource and studentsBindingSource.
I wire everything up like so:
teachersBindingSource.DataSource = teachers;
studentsBindingSource.DataSource = teachersBindingSource;
studentsBindingSource.DataMember = "Students";
teachersDataGridView.DataSource = teachersBindingSource;
studentsDataGridView.DataSource = studentsBindingSource;
And as if by magic when running up on the form selecting an item from the teachers grid changes students grid.
For managing inserts, updates and deletes you will need to implement some sort of change tracking yourself (or use an ORM such as Entity Framework or nHibernate). It is a topic that deserves its own question so read up on those technologies (and look at the blog post I like below) and come back when you have some specific problems.
For this answer I borrowed heavily from this excellent post - the example I've given is complete and avoids a lot of the complexity in that authors example's, but eventually you will probably want to at least know about everything he discusses. Download his demos and have a look.
I have a big problem. I try to bind my WPF DataGrid to a table, created with inner join. I have created a class for the info to convert successfully:
public class NeshtoSi
{
public NeshtoSi() { }
public string ssn;
public string name;
public string surname;
}
And then I create the inner-joined tables. Still when I assign the ItemsSource and all values are transferred properly, but the DataGrid does not visualize them.
var dd = from d in dataContext.Medical_Examinations
join p in dataContext.Patients on d.SSN equals p.SSN
select new NeshtoSi { ssn = d.SSN, name = p.Name, surname = p.Surname };
IQueryable<NeshtoSi> sQuery = dd;
if (!string.IsNullOrEmpty(serName.Text))
sQuery = sQuery.Where(x => x.name.Contains(serName.Text));
if (!string.IsNullOrEmpty(serSurame.Text))
sQuery = sQuery.Where(x => x.surname.Contains(serSurame.Text));
if (!string.IsNullOrEmpty(serSSN.Text))
sQuery = sQuery.Where(x => x.ssn.Contains(serSSN.Text));
var results = sQuery.ToList();
AnSearch.ItemsSource = sQuery;
I hope that someone can help me...
The code that you presented seems ok - it doesn't matter how an object is created - what matters is the object itself.
Rather than showing us this, you should show the xaml.
One more thing - are we talking about DataGridView from winforms or rather the one that comes with WPF Toolkit ?
=======================================
Sorry. I've missed it in the first place - you don't have properties in your class! You've created public fields instead of properties and that's probably the problem.
The code should look like this:
public class NeshtoSi
{
public NeshtoSi() { }
public string ssn{get; set;}
public string name{get; set;}
public string surname{get; set;}
}
I went through this recently, and the answer is outlined in a post of mine that is here
I am doing something wrong .. you know how it is.
I have tried playing around with ItemsSource , DataContext , DisplayMemberPath and SelectedValuePath and I either get a blank list of a list of the ToString method being called in the Person object;
WHAT WOULD REALLY HELP is for someone to publish an answer that works for this example.
I have simplified the problem as I am having difficulty in general with databinding generics.
I have created a simple Generic List of Person and want to bind it to a combo. (also want to try use a ListView too).
I either get a list of blanks or a list of 'xxxx.Person' where xxxx = namespace
<Window x:Class="BindingGenerics.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300">
<Grid>
<ComboBox Name="ComboBox1"
ItemsSource="{Binding}"
Height="50"
DisplayMemberPath="Name"
SelectedValuePath="ID"
FontSize="14"
VerticalAlignment="Top">
</ComboBox>
</Grid>
</Window>
using System.Windows;
using System.ComponentModel;
namespace BindingGenerics
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Person p = new Person();
// I have tried List and BindingList
//List<Person> list = new List<Person>();
BindingList<Person> list = new BindingList<Person>();
p.Name = "aaaa";
p.ID = "1111";
list.Add(p);
p = new Person();
p.Name = "bbbb";
p.ID = "2222";
list.Add(p);
p = new Person();
p.Name = "cccc";
p.ID = "3333";
list.Add(p);
p = new Person();
p.Name = "dddd";
p.ID = "4444";
list.Add(p);
ComboBox1.DataContext = list;
}
}
public struct Person
{
public string Name;
public string ID;
}
}
In your code sample, Person.Name is a field rather than a property. WPF data binding considers only properties, not fields, so you need to change Person.Name to be a property.
Change your Person declaration to:
public class Person
{
public string Name { get; set; }
public string ID { get; set; }
}
(For production code, you'll probably want to use an ObservableCollection<Person> rather than a List<Person> and either make Person immutable or make it implement INotifyPropertyChanged -- but those aren't the sources of your immediate problem.)
In the code shown you're setting ItemsSource twice, the first time in XAML (called by InitializeComponent) to the DataContext of ComboBox1, which can't be determined from what you've posted but it's probably not what you want. After that you're resetting it from code to your list object (here with typos). In this code you're also adding the same instance of Person 4 times and just changing its Name and ID over and over. I suspect a combination of these issues and the fact that you're using a List instead of ObservableCollection are causing the issues in your application.
It would help narrow it down if you could post some actual code you're seeing problems with as what you've put here isn't even compilable.
Well... I'm assuming your actual code has corrected syntax, as the code you pasted in won't compile.
I put this code into a new WPF app and, after new-ing each Person object, my combobox populated fine. You might want to move your population code into a Loaded event, which will ensure the form is properly constructed. Here's the corrected xaml and codebehind (with a few syntax shortcuts):
xaml:
<Grid>
<ComboBox Name="ComboBox1" Height="70"
DisplayMemberPath="Name"
SelectedValuePath="ID" />
</Grid>
codebehind:
public Window1()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Window1_Loaded);
}
void Window1_Loaded(object sender, RoutedEventArgs e)
{
var list = new List<Person>();
Person p = new Person(){Name = "aaaa",ID = "1111"};
list.Add(p);
p = new Person(){Name = "bbbb", ID="2222"};
list.Add(p);
p = new Person(){Name = "cccc", ID="3333"};
list.Add(p);
p = new Person(){Name = "dddd", ID="4444"};
list.Add(p);
ComboBox1.ItemsSource = list;
}