How do you create a table for user-input in WPF? - wpf

I'm quite new to WPF and XAML and have been learning as I go.
I am writing a program in which the user needs to be able to enter data into a table, with pre-defined columns, which is then later sent to a database. I am, however, unable to make a DataGrid that allowes for this behaviour -
I'm using a - XAML is as follows:
<Grid MinHeight="100" MinWidth="600">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="1"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="True">
<DataGrid SelectionMode="Single"
SelectionUnit="Cell"
CanUserAddRows="True"
CanUserDeleteRows="True"
IsReadOnly="False"
AutoGenerateColumns="False"
ItemsSource="{Binding SimpleCollection}">
<DataGrid.Columns>
<DataGridTextColumn Header="Rownummer" Binding="{Binding RowNumber}"/>
<DataGridTextColumn Header="Navn på felt" Binding="{Binding FieldName}"/>
<DataGridTextColumn Header="Forretningsmæssig nøgle" Binding="{Binding BusinessKey}"/>
<DataGridTextColumn Header="Er der datoopl." Binding="{Binding ContainsTimestamps}"/>
<DataGridTextColumn Header="Er der koder, der oversættes via BAS" Binding="{Binding ContainsBASTranslatedCodes}"/>
<DataGridTextColumn Header="Metadata" Binding="{Binding Metadata}"/>
<DataGridTextColumn Header="Evt. bemærkninger" Binding="{Binding AdditionalComments}"/>
</DataGrid.Columns>
</DataGrid>
</ScrollViewer>
</Grid>
<Grid>
The class used in the binding:
public class UserTable
{
public int RowNumber { get; set; }
public string FieldName { get; set; }
public string BusinessKey { get; set; }
public string ContainsTimestamps { get; set; }
public string ContainsBASTranslatedCodes { get; set; }
public string Metadata { get; set; }
public string AdditionalComments { get; set; }
public UserTable()
{
this.RowNumber = RowNumber;
this.FieldName = FieldName;
this.BusinessKey = BusinessKey;
this.ContainsTimestamps = ContainsTimestamps;
this.ContainsBASTranslatedCodes = ContainsBASTranslatedCodes;
this.Metadata = Metadata;
this.AdditionalComments = AdditionalComments;
}
public UserTable(int number, string name, string key, string timestamps, string translated, string meta, string additional)
{
RowNumber = number;
FieldName = name;
BusinessKey = key;
ContainsTimestamps = timestamps;
ContainsBASTranslatedCodes = translated;
Metadata = meta;
AdditionalComments = additional;
}
}
The creation of the Collection:
private ObservableCollection<UserTable> _simpleCollection;
public ObservableCollection<UserTable> SimpleCollection
{
get { return _simpleCollection ?? (_simpleCollection = new ObservableCollection<UserTable>()); }
set { _simpleCollection = value; }
}
(I am unsure where to place this - Have had it in the above mentioned class, and now in the .cs file that corresponds with the XAML)
Above code results in the following UI:
As you can probably see, the user is unable to enter anything into the table - There are no empty rows for data-insertion.
Does anyone have any idea about how to fix this?

To be able to add new rows to DataGrid you can create a ViewModel class and encapsulate your ObservableCollection<T> data collection in it, like i am going to show you.
for purpose of simplifying things let's say we have User model like this:
public class User
{
public int Id { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
}
And let's create a simple view model class:
public class UsersViewModel
{
public UsersViewModel()
{
_users = new ObservableCollection<User>();
}
private ObservableCollection<User> _users;
public ObservableCollection<User> User => _users; // C# 6 feature.
}
Now we need to create an instance of UsersViewModel class and assign it as data context to any element that is parent to your DataGrid in our case let's assign it to the MainWindow Grid DataContext, we can do it in XAML or in code behind:
<Window xmlns:vm="clr-namespace:Namespace.In.Which.UsersViewModel.Lives">
<Window.Resources>
<vm:UsersViewModel x:Key="viewModel"></local:UsersViewModel>
</Window.Resources>
<Grid x:Name="mainGrid" DataContext="{StaticResource viewModel}"></Grid>
...
</Window>
Or you can do the same in code behind like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
mainGrid.DataContext = new UsersViewModel();
}
}
After that you need to bind the Users property of UserViewModel to DataGrid.ItemsSource property:
<DataGrid ...
ItemsSource="{Binding Users}"></DataGrid>
Now if you if you run your Application you will end up with an editable DataGrid.
You can set AutoGenerateColumns to true and the DataGrid will auto generate the correct columns type for your model.
I tested this steps on my machine and they work. Hopefully it helps you to solve your issue.

For the user to be able to add items in the DataGrid, your UserTable class must have a default parameterless constructor defined.
So you will either have to define one or remove the other constructor that accepts parameters:
public class UserTable
{
public int RowNumber { get; set; }
public string FieldName { get; set; }
public string BusinessKey { get; set; }
public string ContainsTimestamps { get; set; }
public string ContainsBASTranslatedCodes { get; set; }
public string Metadata { get; set; }
public string AdditionalComments { get; set; }
public UserTable() { } //<--
public UserTable(int number, string name, string key, string timestamps, string translated, string meta, string additional)
{
RowNumber = number;
FieldName = name;
BusinessKey = key;
ContainsTimestamps = timestamps;
ContainsBASTranslatedCodes = translated;
Metadata = meta;
AdditionalComments = additional;
}
}

Related

How can i add a Combobox selection to a datagrid

I have a datagrid which is bound to my ObservableCollection.
Each column is bound to property of the class wellenelement.
Now i would like to convert the column "Art" to a combobox column, where the user can choose from 3 different options.
How can i create these 3 Combobox items and add it to the datagrid?
<DataGrid AutoGenerateColumns="True" Name="dataGrid1" ItemsSource="{Binding}" >
</DataGrid>
```xaml
```c#
public partial class MainWindow : Window
{
public ObservableCollection<Wellenelement> Welle1;
public MainWindow()
{
InitializeComponent();
Welle1 = new ObservableCollection<Wellenelement>();
dataGrid1.DataContext = Welle1;
}
}
```c#
```c#
public class Wellenelement
{
public string Art { get; set; }
public string UK { get; set; }
public string DA { get; set; }
public string DI { get; set; }
}
```c#
If it's the same for each item you can add a collection to the viewmodel and add it as a resource to your view. Then you can set the resource as the itemssource for the DataGridComboBoxColumn.
ViewModel:
public class MainViewModel
{
public MainViewModel()
{
Wellenelements = new ObservableCollection<Wellenelement>()
{
new Wellenelement()
{
UK = "uk1",
DA = "da1",
DI = "di1"
},
new Wellenelement()
{
UK = "uk2",
DA = "da2",
DI = "di2"
},
};
ArtTypes = new List<string>()
{
"new art","old art", "good art","bad art"
};
}
public ObservableCollection<Wellenelement> Wellenelements { get; set; }
public List<string> ArtTypes { get; set; }
}
View:
<Window ...
xmlns:viewmodels="clr-namespace:WpfApp.Viewmodels"
...>
<Window.DataContext>
<viewmodels:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<CollectionViewSource x:Key="myCollection" Source="{Binding ArtTypes}"/>
</Window.Resources>
<Grid>
<DataGrid AutoGenerateColumns="False"
CanUserAddRows="False"
ItemsSource="{Binding Wellenelements}" >
<DataGrid.Columns>
<DataGridComboBoxColumn ItemsSource="{Binding Source={StaticResource myCollection}}"
SelectedItemBinding="{Binding Art, UpdateSourceTrigger=PropertyChanged}"
Header="Art Combo column"/>
<DataGridTextColumn Binding="{Binding Art}"
IsReadOnly="True"
Header="Selected Art Type"/>
<DataGridTextColumn Binding="{Binding UK}"
Header="UK"/>
<DataGridTextColumn Binding="{Binding DA}"
Header="DA"/>
<DataGridTextColumn Binding="{Binding DI}"
Header="DI"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Wellenelement class:
public class Wellenelement : ObservableObject
{
public string UK { get; set; }
public string DA { get; set; }
public string DI { get; set; }
private string _art;
public string Art
{
get { return _art; }
set
{
_art = value;
OnPropertyChanged(nameof(Art));
}
}
}

EF6, Code First. How to set alternative data source for GridControl column using XAML Devexpress

I have two related entities:
public class Event
{
public string ID { get; set; }
public DateTime EventDate { get; set; }
public string EventData { get; set; }
public string DocID1 { get; set; }
public int DocID2 { get; set; }
public virtual Document Document1 { get; set; }
public virtual Document Document2 { get; set; }
}
public class Document
{
public string ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Number { get; set; }
}
Also created List to display data in WPF:
private ObservableCollection<Event> eventList;
public ObservableCollection<Event> EventList
{
get { return eventList; }
set
{
if (value != eventList)
{
eventList = value;
}
RaisePropertyChanged(nameof(EventList));
}
}
data for my EventList is taken from entity Event:
var query = DBContext.Events.Select(x => x).AsQueryable();
EventList = query.ToList();
What I need is how to set GridControl Column "colFirstName" value to be equal "Document1.FirstName" if DocID1 is not null and "Document2.FirstName" if DocID2 is not null. My XAML code for GridControl is below, could You help how to do it in Xaml, not in ViewModel, or if there is no way to do it in Xaml, what is the best way to do it in ViewModel.
<dxg:GridControl AutoPopulateColumns="False"
ItemsSource="{Binding EventList, UpdateSourceTrigger=PropertyChanged}">
<dxg:GridControl.Columns>
<dxg:GridColumn
x:Name="colEventData"
Width="120"
FieldName="EventData"
Header =" Event data"
Visible="True" >
</dxg:GridColumn>
<dxg:GridColumn
x:Name="colFirsnName"
Width="120"
FieldName="Document1.FirstName"
Header="First Name"
Visible="True"
VisibleIndex="1" />
</dxg:GridControl.Columns>
</dxg:GridControl>
You can use the UnboundExpression property to create a column with a custom expression. For example, to conditionally show values from different properties, use the Iif function:
<dxg:GridColumn FieldName="CustomColumn" x:Name="colFirsnName"
UnboundExpression="Iif(IsNullOrEmpty([DocID1]), [Document2.FirstName], [Document1.FirstName])"
UnboundType="String"/>
This kind of logic should be implemented in the model or view model class. Remember that XAML is only a markup language.
Just create another property that returns the first match and bind to this one. If your entity classes are auto-generated, you could create a new partial class:
public partial class Event
{
public string DocName
{
get
{
if (Document1 != null)
return Document1.FirstName;
if (Document2 != null)
return Document1.LastName;
return null;
}
}
}
<dxg:GridColumn
x:Name="colFirsnName"
Width="120"
FieldName="DocName"
Header="First Name"
Visible="True"
VisibleIndex="1" IsReadOnly="true" />

How do I sort a WPF treeview that has items bound to the properties of an Item subclass?

I have two classes,
public class BookItem
{
public string BookID { get; set; }
public string ItemID { get; set; }
public Item Item { get; set; }
public ItemType Type { get; set; }
public string ParentID { get; set; }
public string BoxID { get; set; }
public string StyleID { get; set; }
public string NotesID { get; set; }
public string Code_XAML { get; set; }
public string Description_XAML { get; set; }
public CompositeCollection SubItems { get; set; }
}
public class Item : ClaunchBaseClass
{
public string ItemID { get; set; }
public int Type { get; set; }
public string Code { get; set; }
public string Description { get; set; }
private BookList _books = new BookList();
public BookList Books { get {return _books;} set { _books = value; }}
}
and I've created the following XAML:
<pre>
<TreeView Name="tvList" Grid.Row="2" MouseDoubleClick="tvList_MouseDoubleClick">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="x:Type j:BookItem" ItemsSource="{Binding SubMenu}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Item.Code}" Grid.Column="0" />
<TextBlock Text="{Binding Item.Description}" Grid.Column="1"/>
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<code>
This XAML binds the treeview items to the collection of book items and displays the Item subclass's Description and Code, the treeview populates and displays correctly but now I want to sort the treeview on either Item.Code or Item.Description and have tried the following with no results:
<pre>
var bookItemsSort = CollectionViewSource.GetDefaultView(_bookItemList) as ListCollectionView;
tvList.ItemsSource = _bookItemList; //bind the book items to the treeview
bookItemsSort.SortDescriptions.Clear();
bookItemsSort.SortDescriptions.Add(new SortDescription(sort, Ascending));
<code>
I've had this code work correctly for other treeviews so I can only guess it is a problem with binding to a subclass.
While the answers here provided partial answers to my question, none of them gave the answer that I needed.
The most sensible solution for this problem was to write my own object comparer for this object type and sort the underlying list and then re-bind the new list to the treeview. This allowed for comparing sublasses at any nested level which I couldn't make work any other way :)
You need to get the default view for each sub-list, and apply a CollectionViewSource sorting to it. The code you posted only affects the top level items.
Bind your TreeView.ItemsSource to the DefaultView. SortDescriptions will not change your DataList, only the View of it.
tvList.ItemsSource = bookItemsSort;
See Bea Stollnitz blog: How can I sort a hierarchy?

WPF: How do I start styling a DataGrid that was built using MVVM?

I have a list of names that I'd like to have bound to a datagrid for editing/sorting/etc. But, I don't like how the DataGrid is displayed at all. Columns are placed in Alphabetical order when I really want a custom order (and I wish I could hide the ID column, or make that column not editable). I'm not sure how to start doing any of this...
NOTE: I removed a lot of "common" code (ex: INotifyPropertyChanged code...)
//PersonModel.cs
public class PersonModel
{
public Int32 ID { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
}
//PersonViewModel.cs
public class PersonViewModel
{
public PersonViewModel()
{
Init();
}
public PersonViewModel(ObservableCollection<PersonModel> persons)
{
Init(person);
}
private void Init(ObservableCollection<PersonModel> persons = null)
{
Persons = person ?? new ObservableCollection<PersonModel>();
}
public ObservableCollection<PersonModel> Persons { get; set; }
}
//PersonView.xaml
<UserControl ...
...
<DataGrid ItemsSource="{Binding Persons}" />
...
</UserControl>
Unless you tell it otherwise, the DataGrid infers columns via reflection. If you want to take control, you can:
<DataGrid ItemsSource="{Binding Persons}">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/>
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName}"/>
</DataGrid.Columns>
</DataGrid>

Silverlight 3 DataForm - How to populate DataFormComboBoxField from enumeration

XAML
<df:DataForm x:Name="MobCrud"
AutoEdit="True"
AutoCommit="True"
AutoGenerateFields="False"
VerticalAlignment="Top"
CommandButtonsVisibility="All"
Header="Mob Details"
CanUserAddItems="True"
CanUserDeleteItems="True"
CurrentItem="{StaticResource newMob}"
>
<df:DataForm.Fields>
<df:DataFormTextField Binding="{Binding Name}" FieldLabelContent="Name" />
<df:DataFormTextField Binding="{Binding Title}" FieldLabelContent="Title"/>
<df:DataFormComboBoxField x:Name="AuraList" Binding="{Binding Aura}" FieldLabelContent="Aura"/>
</df:DataForm.Fields>
Code:
public enum Auras
{
Holy,
Fire,
Frost,
}
public class MobDetail : IEditableObject
{
public string Name { get; set; }
public string Title { get; set; }
public Auras Aura { get; set; }
public override string ToString() { return Name; }
public void BeginEdit(){}
public void EndEdit(){}
public void CancelEdit(){}
}
The DataForm ItemsSource is bound to an ObservableCollection()
What do I need to do to populate and initialize the dropdown?
Answer is to use a converter:
<df:DataFormComboBoxField
x:Name="AuraList"
Binding="{Binding Aura, Mode=TwoWay,
Converter={StaticResource enumSelectedValueConverter}}"
FieldLabelContent="Aura"/>
and set the ItemsSource on the form Loaded event
(MobCrud.Fields[2] as DataFormComboBoxField).ItemsSource =
Enums.GetStringArray(typeof(Auras));
See here for the full story:
Creating-Rich-Data-Forms-in-Silverlight-3-Customization

Resources