CRUD operations with an unknown type - wpf

I am trying to make a program that gives you a CRUD interface for List of any objects you give it. That includes:
Showing all of their properties inside a ListBox
The ability to insert a new object
The ability to update an object
The ability to delete an object
Keep in mind that, at the compile-time, I have no idea what kind of objects I am getting. For example, I want to have a TextBlock for each of the properties simply listed inside ListBox's DataTemplate. So how would I do the data binding if I don't know the name of the property? Also, how would I generate an insertion form when I don't know property names?
And finally, is it possible to do it using pure MVVM Pattern, without any Code-Behind?
Thanks

One option: Wrap PropertyInfo in a PropertyInfoViewModel so you can bind to it's value:
class PropertyInfoViewModel
{
Object CRUDObject { get; set; }
PropertyInfo PropertyInfo { get; set; }
Object Value {
get
{
return PropertyInfo.GetValue(CRUDObject);
}
set
{
PropertyInfo.SetValue(CRUDObject, value);
}
}
}
You could have an ObservableCollection in your CRUDObjectViewModel, populated when you create it or change the CRUD it's attached to (Look up reflection if confused by this).
Use a template selector to choose a particular editor to display for the PropertyInfoViewModel:
public class PropertyTypeTemplateSelector : DataTemplateSelector
{
public DataTemplate BooleanTemplate { get; set; }
public DataTemplate GuidTemplate { get; set; }
public DataTemplate StringTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
PropertyInfo propertyInfo = (item as PropertyInfoViewModel).PropertyInfo;
if (propertyInfo.PropertyType == typeof(Boolean))
{
return BooleanTemplate;
}
else if (propertyInfo.PropertyType == typeof(Guid))
{
return GuidTemplate;
}
else if (propertyInfo.PropertyType == typeof(String))
{
return StringTemplate;
}
return null;
}
}
You could use it like this:
<ListBox ItemsSource="{Binding Properties}">
<ListBox.Resources>
<DataTemplate x:Key="BooleanTemplate">
<CheckBox Content="{Binding PropertyInfo.Name}" IsChecked="{Binding Value}"/>
</DataTemplate>
<DataTemplate x:Key="GuidTemplate">
<StackPanel>
<TextBox Text="{Binding PropertyInfo.Name}"/>
<TextBox Text="{Binding Value, ValueConverter={StaticResources MyGuidConverter}}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="StringTemplate">
<StackPanel>
<TextBox Text="{Binding PropertyInfo.Name}"/>
<TextBox Text="{Binding Value}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="Null"/>
</ListBox.Resources>
<ListBox.ItemTemplateSelector>
<helpers:PropertyTypeTemplateSelector BooleanTemplate="{StaticResource BooleanTemplate}"
GuidTemplate="{StaticResource GuidTemplate}"
StringTemplate="{StaticResource StringTemplate}"/>
</ListBox.ItemTemplateSelector>
</ListBox>
Might have to think about how to deal with changes/updates though, as this isn't using NotifyPropertyChanged to keep the UI up to date.
I've not tested any of this, but it should work, I think.

This control WPFCrudControl may fit with your problem.
A generic WPF CrudControl implemented based on the MVVM pattern. It gives a huge productivity boost for straightforward CRUD operations (Add, Edit, Delete, Validate, Listing with sorting, paging and searching). The control abstracts both the UI and business logic, so it requires relatively minimal coding effort, while keeping it possible to customize its behavior.

Related

WPF - How to implement DataTemplate with complex logic?

I am currently transferring my app from WinForms to WPF.
Since I'm new in WPF, I stucked at creating DataTemplates for my treeView items. The screenshot shows how my treeview looked in WinForms version, and I need to get close result in WPF.
(My WinForms treeview)
As you can see, my DataTemplate's logic should take into account these factors:
Node type / defines which icon and fields combination will be displayed for particular item (node). App has about 7-8 node types. Type stored in separate node's field.
Variable values / I need to replace with text if null, etc
Numeric variable values / e.g.: set gray color if zero, etc.
Other properties / e.g.: adding textblocks depending on boolean fields.
And so on...
All these factors result into huge amount of possible item params combinations.
Also I'm using DevComponents WPF DotNetBar AdvTree to divide item properties into columns. I presume I should create 'sub templates' for different field sets and compose from them the entire DataTemplate for each column.
I've learned about triggers, and have to say that implementing my logic with triggers will make my subtemplates huge anyway.
(Current state of my WPF treeview)
So here are my questions:
Are there any ways to dynamically compose complex templates with C# code (without creating raw XAML and loading it at runtime)?
Maybe I should use completely different way (instead of using DataTemplate)? In Winforms I just used OwnerDraw mode, so the task was MUCH easier than in WPF :(
And how to display nested properties inside template? e.g.: Item.Prop.Subprop1.Subprop2.Targetprop.
PS: English is not my first language, sorry for your eyes.
1) The answer is yes.
For exemple if you want to define a template in your window for a simple string
public MainWindow()
{
InitializeComponent();
DataTemplate template = new DataTemplate(typeof(string));
FrameworkElementFactory borderFactory = new FrameworkElementFactory(typeof(Border));
borderFactory.SetValue(Border.PaddingProperty, new Thickness(1));
borderFactory.SetValue(Border.BorderThicknessProperty, new Thickness(1));
borderFactory.SetValue(Border.BorderBrushProperty, Brushes.Red);
FrameworkElementFactory textFactory = new FrameworkElementFactory(typeof(TextBlock));
textFactory.SetBinding(TextBlock.TextProperty, new Binding
{
Mode = BindingMode.OneWay
});
borderFactory.AppendChild(textFactory);
template.VisualTree = borderFactory;
myControl.ContentTemplate = template;
}
And in the Window you just put something like
<ContentControl x:Name="myControl" Content="Test text" Margin="10"/>
Your content control will render the string surrounded by a red border.
But as you anc see it is really complex to define your templates in this way.
The only scenario where i could imagine this approache is for some kind of procedurally generated templates.
Another way is to generate a string for the template and then load it with XamlReader:
string xaml = "<Ellipse Name=\"EllipseAdded\" Width=\"300.5\" Height=\"200\"
Fill=\"Red\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"/>";
object ellipse = XamlReader.Load(xaml);
2) I don't really see the need to generate templates in code behind. For exemple for this kind of data structure:
public class User
{
public string Name { get; set; }
public User Friend { get; set; }
}
public class RootNode
{
public string Title { get; set; }
public User User { get; set; }
public List<Node> Nodes { get; set; }
}
public class Node
{
public string Title { get; set; }
public List<SubNode> SubNodes { get; set; }
}
public class SubNode
{
public string Title { get; set; }
}
You can define this type of template:
<Window
...
Title="MainWindow" Height="350" Width="525" >
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:RootNode}" ItemsSource="{Binding Nodes}">
<StackPanel x:Name="spContainer" Orientation="Horizontal">
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="{Binding User.Friend.Friend.Name}"/>
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding User}" Value="{x:Null}">
<Setter TargetName="spContainer" Property="Background" Value="Yellow"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Node}" ItemsSource="{Binding SubNodes}">
<TextBlock Text="{Binding Title}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:SubNode}" ItemsSource="{Binding Nodes}">
<TextBlock Text="{Binding Title}"/>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding RootNodes}"/>
</Grid>
</Window>
As you can see you can define a template by data type, you can also use triggers to modify the behavior in specific cases, you could also use som binding converters...
3) You can bind to nested properties just like to normal ones :
<TextBlock Text="{Binding User.Friend.Friend.Name}"/>
However in some cases more than two level bindings could fail (fail to resolve or fail to update when property changes, ...)

Make only one of many controls visible based on value of another control

I have a ComboBox bound to a collection of objects defined as this.
public class TierOption
{
public string Option { get; set; }
public Type DataType { get; set; }
}
public class TierOptions : ObservableCollection<Tier1Option>
{
}
I have 3 other controls related to this ComboBox, which are a TextBox, ComboBox, or a WPFToolKit:DatePicker.
I need to show only the related control which corresponds to the datatype(Type) of the object selected in the first ComboBox and neither of the others.
Pseudo Code Example:
(Probably too close to butchered C# but hopefully it conveys the idea)
switch (ComboBox.SelectedItem.DataType)
{
case String:
TextBox.Visibility = Visibility.Visible;
ComboBox.Visibility = Visibility.Hidden;
DatePicker.Visibility = Visibility. Hidden;
break;
case DateTime:
TextBox.Visibility = Visibility.Hidden;
ComboBox.Visibility = Visibility.Hidden;
DatePicker.Visibility = Visibility. Visible;
break;
<...so forth and so on...>
}
My attempts have resulted in very non-wpf looking convoluted messes which don't work regardless. Being new to wpf I'm trying very hard to stay true to the best design practices.
Thank you!
You can play with DataTemplate with DataType property
<...Resources>
<DataTemplate DataType="{x:Type sys:String}">
<TextBox Text="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type DateTime}">
<DatePicker .../>
</DataTemplate>
...
</...Resources>
<ContentControl Content="{Binding SelectedItem, ElementName=myComboBox}"/>
<ComboBox ItemsSource="{Binding ...}"/>
The code above is just the idea, you could have to make adjustements. For example you won't be able to modify a string item itself (you could have to encapsulate each item of your list)
If your list contains all items of the same type, you can use a ContentTemplateSelector on contentControl.
ContentControl Content="{Binding SelectedItem, ElementName=YourCombBox}" ContentTemplateSelector="{StaticResource YourTemplateSelector}"
MSDN DOC about ContentControl.ContentTemplateSelector Property
Bind to the detail visibility to ElementName=ComboBox Path=SelectedItem.DataType. And you will need to use a converter that returns visibility. You will need two converters return opposite answers. If you have more than 2 combination then some more in the line of Jonas.
I assumed Type was a system class and it appears to be a custom class. Extend that class to have additional properties. Even if Type was a system type you could just create a class that implements it and extend it.
public Visibility TextBoxVisibility { get; }
public Visibility ComboBoxVisibility { get; }
...
Then on TextBox bind the visibility
Visisbility="{binding ElementName=Combobox Path=SelectedItem.DataType.TextBoxVisibility]";

How do you customize columns in silverlight 4 data binding

This is a very basic question. I have a grid, whose data context is bound to a entity framework service. I simply bound the context to service and I can see the data that is getting bound properly. Now, I want to change couple of coulmns to special controls. Like one column has true or false value and that column I want to display a radio button. One column is date value, I want to display date control. How would one go about doing it?
Thanks.
I'm not exactly sure how to do the Radio Button portion of this, but something like this may get you started:
<ListBox x:Name="LayoutRoot" ItemsSource="{Binding Collection}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Text}"/>
<CheckBox Content="True" IsChecked="{Binding Checked, Mode=TwoWay}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In this case, you would have a checkbox being bound to the boolean value. I'm not exactly sure what you are using for a date control, but you should be able to place that in the stackpanel also and bind it to the dateproperty of your item.
In the above example, 'Collection' is an observable collection of 'MyObject' which is shown below:
MyObject.cs
public class MyObject
{
public string Text { get; set; }
public bool Checked { get; set; }
public bool InverseChecked { get; set; }
public DateTime Date { get; set; }
}
I also understand that you were using a grid, and I'm showing a ListBox. Not sure if this would work for you, but this is how we've approached it in the past.
Hope this helps!

Silverlight 3 ComboBox ItemTemplate binding

I have a simple ComboBox in my Silverlight 3 application. I want to populate it from an ObservableCollection. The list holds a class that has a Name(string), and a Selected(bool) property. The combo box has as many items as I have in the list, but I cannot seem to get the list data to appear.
Any help would be appreciated.
<ComboBox x:Name="cmbCategory" Grid.Column="3">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<CheckBox IsChecked="{Binding Selected}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
...
private class cmbCategoryClass
{
public string Name { get; set; }
public bool Selected { get; set; }
}
private ObservableCollection<cmbCategoryClass> _categories;
....
cmbCategory.DataContext = _categories;
cmbCategory.ItemsSource = _categories;
I can't tell from your code if this is a Codebehind or a ViewModel. I am guessing that you are actually populating the _categories list in code so that it contains at least one cmbCategoryClass object. Try removing the line that sets the DataContext to _categories as your ItemsSource may be looking for a _categories property on the DataContext Check the Output window in Visual Studio when running in debug mode for clues to data binding failures.

WPF HiercharchicalDataTemplate.DataType: How to react on interfaces?

Problem
I've got a collection of IThings and I'd like to create a HierarchicalDataTemplate for a TreeView. The straightforward DataType={x:Type local:IThing} of course doesn't work, probably because the WPF creators didn't want to handle the possible ambiguities.
Since this should handle IThings from different sources at the same time, referencing the implementing class is out of question.
Current solution
For now I'm using a ViewModel which proxies IThing through a concrete implementation:
public interface IThing {
string SomeString { get; }
ObservableCollection<IThing> SomeThings { get; }
// many more stuff
}
public class IThingViewModel
{
public IThing Thing { get; }
public IThingViewModel(IThing it) { this.Thing = it; }
}
<!-- is never applied -->
<HierarchicalDataTemplate DataType="{x:Type local:IThing}">
<!-- is applied, but looks strange -->
<HierarchicalDataTemplate
DataType="{x:Type local:IThingViewModel}"
ItemsSource="{Binding Thing.SomeThings}">
<TextBox Text="{Binding Thing.SomeString}"/>
</HierarchicalDataTemplate>
Question
Is there a better (i.e. no proxy) way?
Another alternative (similar to jing's solution): If you only have one type of item, you can set the ItemTemplate directly. Then you don't need to set a key or a datatype.
In your ViewModel:
public ObservableCollection<IThing> Thingies { get; private set; }
In the View:
<TreeView ItemsSource="{Binding Thingies}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SomeThings}">
<TextBox Text="{Binding SomeString}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The reason for this is that the default template selector supports only concrete types, not interfaces. You need to create a custom DataTemplateSelector and apply it to the ItemTemplateSelector property of the TreeView. I can't find the URL where I found an example of it, but hopefully with this info, you can Google it.
Another solution is you give a key to the HierarchicalDataTemplate and put it in the Windows.Resources, and manually reference to it in the TreeView. <TreeView ItemDataTemplate={StaticResource templateKey}/>
But that limits the autoselection of data template according to data type, which is provided by WPF TreeView.

Resources