How to Clone a whole grid of Controls? - wpf

I have the following code and basically what i am not able to figure out is how to clone the whole grid and make a blank copy of them side by side.... for a clear understanding this is something to do with hospital application and the grid is related to a pregnancy so when said 'ADD CHILD' button a whole new grid should be created during run time, thanks for the help below is a link that might help people cause i tried it but not sure how to display it
How can you clone a WPF object?

You should put the object you are want to "clone" in a DataTemplate and reference this template from an ItemsControl, then when you need another grid add another item to the items control (or even better to the list the control is bound to) and the ItemsControl will create a new grid and bind it the new object.
For an example take a look at this post on my blog.
Here is an example for this application (I left only the relevant parts and I didn't test it, so there are probably some typos there):
<Window ... >
<Window.Resources>
<DataTemplate x:Key="ChildTemplate">
<Grid>
...
<TextBlock Text="Delivery Date:" Grid.Column="0" Grid.Row="0"/>
<TextBox Text="{Binding DeliveryDate}" Grid.Column="1" Grid.Row="0"/>
<TextBlock Text="Delivery Time:" Grid.Column="0" Grid.Row="1"/>
<TextBox Text="{Binding DeliveryTime}" Grid.Column="1" Grid.Row="1"/>
...
</Grid>
</DataTemplate>
</Window.Resources>
...
<Button Content="AddChild" Click="AddChildClick"/>
...
<ScrollViewer>
<ItemsControl ItemsSource="{Binding AllChildren}" ItemsTemplate="{StaticResource ChildTemplate}">
<ItemsControl.PanelTemplate>
<ItemsPanelTemplate><StackPanel Orientation="Horizontal"/></ItemPanelTemplate>
<ItemsControl.PanelTemplate>
</ScrollViewer>
...
</Window>
And in cs:
Set an object with all the form data as the Window's DataContext. I'll call this class PostDelveryData.
Create another class with the repeating data. I'll call it ChildDeliveryData.
Add a property of type ObservableCollection<ChildDeliveryData> called AllChildren to PostDeliveryData; it's important it'll be ObservableCollection and not any other type of collection.
Now, for the magic:
private void AddChildClick(object sender, RoutedEvetnArgs e)
{
((PostDeliveryData)DataContext).AllChildren.Add(new ChildDeliveryData());
}
And when you add the new item to the list another copy of the entire data template will be added.

I'm not sure that you're using the correct approach here. I would approach the problem by creating a "ChildGridControl" with a Child property, and let the Child property handle the databinding. Adding a new child to the GUI would involve creating a new instance of the ChildGridControl.

If I am understanding correctly, you should create a UserControl, which wraps your Grid and subsequent controls inside. And use this User control anywhere you wanted to replicate that UI.

Related

Showing UserControl once at the time with DataTemplateSelector

I have a couple specific user controls to Show some Content, e.g. simple like Image, WebControl but also two complex specific custom controls drawing on a canvas.
Now I thought using the DataTemplateSelector to handle the different UserControls. I actully used this http://tech.pro/tutorial/807/wpf-tutorial-how-to-use-a-datatemplateselector as a reference.
I changed the code so the form loads the UserControls dynamically (according to the file extension) in the following collection:
ObservableCollection<string> _pathCollection = new ObservableCollection<string>();
The only difference to the reference is now I want to navigate back and forward to the next control by showing one control only at the time. Which control should I use instead of ListView?
<Grid>
<ListView ScrollViewer.CanContentScroll="False"
ItemsSource="{Binding ElementName=This, Path=PathCollection}"
ItemTemplateSelector="{StaticResource imgStringTemplateSelector}">
</ListView>
</Grid>
How do I need to bind it to the template (equal to ItemTemplateSelector above)? WPF is still very new to me and I am learning.
Use a ContentControl. Bind your current item to the Content-property and the DataTemplateSelector to the ContentTemplateSelector-property.
<ContentControl Content="{Binding Path=CurrentItem, Mode=OneWay}", ContentTemplateSelector="{StaticResource imgStringTemplateSelector}" />
Your CurrentItem should be a DependencyProperty or a INotifyPropertyChanged-property of your DataContext. When you change your CurrentItem, the ContentControl will update the template automatically with help of your TemplateSelector.

Two-way databinding to ObservableCollection on Silverlight TreeView using DragDropTarget

Here's the question at its most basic: how do I listen for an update of what is changing in a TreeView control modified via a DragDropTarget?
So here's my deal: I have a TreeView that holds agenda items. All are of the same data type (WCFAgendaItem), and are loaded into a hierarchy with children expressed as a property ChildItems. The whole thing is wrapped up in an ObservableCollection and bound to the TreeView using MVVM Light. Works great to view. I also want users to be able to use drag and drop to reorder, reorganize and add new items to this agenda coming from a variety of other sources (one example is a ListView of image slides). All new items would also have the same data type of WCFAgendaItem, for consistency's sake and easy serialization.
Here's my issue: dragging and dropping works beautifully on the UI using the Toolkit's drag drop functionality. But I have no idea how to get the ViewModel to understand changes to the contents of the TreeView.
Code from the view (Agenda.xaml):
(up top)
<UserControl.Resources>
<AHHSTeam_SLClassroomManagerMVVM_Helpers_Converters:BooleanVisibilityConverter x:Key="BooleanVisibilityConverter"/>
<sdk:HierarchicalDataTemplate x:Key="hdtAgenda" ItemsSource="{Binding ChildItems, Mode=TwoWay}" >
<Grid HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ImageThumbnailWidth}" />
<ColumnDefinition Width="250" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding ThumbnailURL}" Width="{Binding ImageThumbnailWidth}" Height="{Binding ImageThumbnailHeight}" Visibility="{Binding HasImage, Converter={StaticResource BooleanVisibilityConverter}}" >
<ToolTipService.ToolTip>
<Image Source="{Binding ResizedImageURL}" />
</ToolTipService.ToolTip>
</Image>
<TextBlock Grid.Column="1" Text="{Binding Title}" TextWrapping="Wrap" />
</Grid>
</sdk:HierarchicalDataTemplate>
<Style TargetType="sdk:TreeViewItem" >
<Setter Property="IsExpanded" Value="True" />
</Style>
</UserControl.Resources>
(later on)
<controlsToolkit:TreeViewDragDropTarget Grid.Row="1" Grid.Column="0" x:Name="ddtAgenda" AllowDrop="True"
HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" >
<sdk:TreeView Width="375" ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Visible" ItemsSource="{Binding DailyAgenda, Mode=TwoWay}" ItemTemplate="{StaticResource hdtAgenda}">
</sdk:TreeView>
</controlsToolkit:TreeViewDragDropTarget>
ViewModel code (AgendaViewModel.cs) --> I tried listening for CollectionChanged, so far that doesn't seem to work
(in constructor)
//add notification of agenda changes
DailyAgenda.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(DailyAgenda_CollectionChanged);
(event)
void DailyAgenda_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
System.Windows.MessageBox.Show("Daily agenda updated, now has " + e.NewItems.Count.ToString() + " top-level elements.");
}
Code from model (WCFAgendaItem.cs)
[ContentProperty("ChildItems")]
public partial class WCFAgendaItem: INotifyPropertyChanged
{
private ObservableCollection<WCFAgendaItem> _childItems = new ObservableCollection<WCFAgendaItem>();
public ObservableCollection<WCFAgendaItem> ChildItems
{
get
{
return _childItems;
}
set
{
_childItems = value;
}
}
...
I am pretty sure that I get that listening for CollectionChanged isn't right in any case, given that this data doesn't just change at the top level. I looked at EventToCommand in Blend (MVVM Light, remember) but the only TreeView-specific event appears to be SelectionChanged, which doesn't seem right either. I looked at putting an EventToCommand trigger on the TreeViewDragDropTarget, but aren't those methods about overriding how the UI interactions happen? I don't think INotifyPropertyChanged on WCFAgendaItem is right for this either: although I'm going to want that later for editing item titles, it doesn't seem like it'll help me when items get moved around.
Maybe what I'm looking for is a stretch, but what I really want to have happen is for Silverlight to understand that the databinding works both ways on the ordering and contents of the WCFAgendaItem collection, and do all the collection reworking itself based on UI interactions. Then I could just listen for an update event after the collection is reworked - after that I can just crawl the modified ObservableCollection bound to the TreeView, and flatten/serialize/update via WCF.
Failing the ideal situation: I'm willing to crawl TreeViewItems if need be, but even if that's what I need to do I'm stuck on when to do it. Plus I need a way to pass all that back to the ViewModel so I'm not writing code behind. Do I need to attach to Drop() and rework the dropping logic? I found several old articles about custom drag drop implementations starting from the Toolkit, but nobody mentions how to save out the modified TreeView, especially in an MVVM situation.
finally {
While typing this out I found this article which may be useful, though that's a fair amount of work in the ViewModel. This is promising and I'll investigate, but I'm still holding out hope for something simpler. Also it looks like the Toolkit events have changed a little since the article was written.
}
I also had issues implementing this type of DragDrop functionality. The root cause seemed to be that neither the ItemDragCompleted event (EventHandler) nor ItemDroppedOnSource (DragEventHandler) pass the index at which the item was dropped.
I ended up subclassing the DragDropTarget in order to expose the protected method:
int GetDropTargetInsertionIndex(TItemsControlType dropTarget, DragEventArgs args)
I then used an attached behavior to assume responsibility for inserting items at the specified index into to the underlying collections.
I'm afraid the underlying code is too expansive to include in a StackOverflow answer (mainly due to extensive decoupling) but it's on my list of subjects to blog about. In the mean time, I hope the information above helps, it was certainly the key to the solution for me.
Ian

WPF "partial forms"

I'm making an application for DB migrations. I made a multithreaded framework with WPF GUI. I put someting like this in my namespace/folder:
class Something : Migrator {
public override Run(){
//I would need this
string valueOfMyCustomFieldOnForm = xyz.Text; //example
int count = 500;
for(int i = 0; i < 500; i++){
//do something here
OnProgressChanged(...); //call event, GUI is updated
}
OnCompleted(...); //migration completed
}
}
Then using reflection I put all classes in that namespace onto dropdown list. When I choose one in a list and click Start, the Thread with code in Run method is started.
DB Host: TEXTBOX
DB Username: TEXTBOX
DB Password: TEXTBOX
--
Migrator custom field 1: TEXTBOX
Migrator custom field 2: TEXTBOX
...
--
List with migrated items - irrelevant
There are few commong field on GUI (like database host, username etc...). But for some of those migrators I would need custom fields on GUI (for example 3 extra textbox fields).
What is the best way to do this in WPF? I need part of the GUI to be dynamic.
There's a lot of seemingly-irrelevant information in your question, which - I think - is really about mechanisms for creating metadata-driven UIs in WPF. Here's a way to approach that problem:
Suppose that you want to build a property-sheet-like UI: a grid that displays a row for each property, with a prompt and an input control of some kind. To do this, you're going to need a collection of objects, with each item in the collection including properties that describe the property and its value. A simple design would be a class that exposes a Prompt property and a Value property and that implements change notification.
Once you have created and populated this collection, you can implement an ItemsControl that displays it in a grid:
<ItemsControl ItemsSource="{Binding Properties}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="PropertyViewModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Prompt"/>
<ColumnDefinition SharedSizeGroup="Value"/>
</Grid.ColumnDefinition>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
</Grid>
<Label Content="{Binding Prompt}"/>
<TextBox Grid.Column="1" Text="{Binding Value, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is pretty simple - the most complicated thing about it is using Grid.IsSharedSizeScope so that all of the grids that this control creates use the same column widths. You could also use a ListView instead of an ItemsControl, though using a ListView for this introduces a bunch of issues surrounding focus and selection that you may not want to deal with.
Note that because of the magic that is WPF template matching, you could conceivably implement the Value property as an object, and create different templates to handle the different possible types of the Value property - just like a real property sheet does. To do this, you'd create a template for each type, e.g.:
<DataTemplate DataType="{x:Type System:String}">
<TextBox Text="{Binding Value, Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type System:DateTime}">
<DatePicker Value="{Binding Value, Mode=TwoWay}"/>
</DataTemplate>
etc. Then you'd change the template for the PropertyViewModel so that instead of showing the Value in a TextBox, it uses a ContentPresenter, e.g.:
<ContentPresenter Grid.Column="1" Content="{Binding}"/>

How do I bind a generic window to an arbitrary view model at runtime, using DataTemplates?

I have a large number of ViewModel classes. For each of these classes, there is a corresponding .xaml file which is a 'UserControl'. In my App.xaml, I have them registered as DataTemplates, like so:
<DataTemplate DataType="{x:Type viewModel:MainMenuViewModel}">
<view:MainMenuView/>
</DataTemplate>
With the idea being that WPF will be able automatically swap in the necessary user controls at runtime. For example, this works:
<Grid>
<StackPanel>
<TextBlock Text="SuperApp" />
<ItemsControl>
<ViewModels:MainMenuViewModel/>
</ItemsControl>
</StackPanel>
</Grid>
In that the entry "MainMenuViewModel" is automatically replaced by the MainMenuView, bound to the MainMenuViewModel. Great. My current goal is now this: I want to have a button, on, say, a view embedded in the MainMenuView, which opens a popup window, which will have a new ViewModel inside. The idea is to set it up so that I have a single 'generic' popup form, in which I embed an arbitrary ViewModel, and let WPF handle actually rendering it with DataTemplates, similar to the above. So I have a command bound to a button, like so:
<Button Command="{Binding Path=LaunchInStandaloneForm}" Content="Rip Out"/>
Which successfully creates a new window, sets the dataContext equal to the appropriate ViewModel, and shows the window.
The question is: How do I set up the XAML of this popup window so that it will render the appropriate DataTemplate for the ViewModel which is the DataContext? I've tried:
<Grid>
<ItemsControl ItemsSource="{Binding Path=.}">
</ItemsControl>
</Grid>
, but it comes up blank. Any pointers?
To set the ItemsSource to the DataContext, use ItemsSource={Binding}. That assumes that the DataContext is an enumerable collection of your View Model objects.
Updating with correct answer:
Use a ContentControl :)
Hope that helps.
The accepted answer here shows how to change templates at runtime. You should be able to dig out the answer from that. Any questions just shout.
How to modify silverlight combobox data display
Hope that helps

If you were going to build an org chart builder in Silverlight, what base class would you use for creating the boxes of the chart?

For this question, let us assume that we will want to show the face of the employee, title, department, and whether they like PiƱa coladas/getting caught in the rain.
Perhaps it might look something like the following:
http://www.edrawsoft.com/images/examples/Photo-Org-Chart-Full.png
Would you use a...
System.Windows.Control.UserControl?
FrameworkElement?
UIElement?
Canvas
Why? As always, thank you for your advise! I greatly appreciate it!
If I had to create a org chart control with advanced layout I would probably derive from Control, and create a "real" templated control in a similar manner as e.g. the TreeView control. This is probably the most advanced route to create a new control, but also the most powerful.
You may also be able to modify the control template of a TreeView, and make it grow downwards from the center instead of left and down from the upper left corner, but it will probably be difficult or impossible to customize the layout of the various levels as the TreeViewItem doesn't carry any extra information to describe the layout of a particular node.
In fact I did recently some experiments modifying the TreeView control template, but I stumbled upon something I didn't understand. Luckily I figured out what I did wrong, and you can see how it is possible to change the orientation of TreeView child items from vertical to horizontal in my question here on Stack Overflow.
I've seen a website that uses TreeViewItem and ControlTemplates, but I can't find it at the moment, I think it was on CodeProject.
Another idea I was playing with recently is use 2 usercontrols, itemcontrols and stackpanels.
Here's an example of a an OrgBar rectangle with text under it and it renders it's children in OrgGroup control by setting the ItemSource to it's children collection recursively. You can put the root orgbar on a canvas and play around with paths for the arrows. I tried to point out the basics but if you need more I can fill in the blanks.
Public Class OrgBarDataNode
Public Property BarColor as New SolidColorBrush(Colors.Red)
Public Property BarName As String
Public Property Children as New ObservableCollection(Of OrgBarDataNode)
End Class
Class MainPage
...
Public Sub Loaded
Dim Root as New OrgBarDataNode With {.BarName = "Root"}
Dim Child1 as New OrgBarDataNode With {.Barname = "Child1"}
Root.Children.Add(Child1)
LayoutRoot.Children.Add(Root)
End Sub
...
End Class
<UserControl x:Class="OrgBar">
<Grid>
<StackPanel ToolTipService.ToolTip="{Binding BarName}" Cursor="Hand">
<Rectangle Fill="{Binding BarColor}" Style="{StaticResource RecStyle}"/>
<TextBlock Text="{Binding BarName}" HorizontalAlignment="Center"
Margin="0,10,0,0" />
<local:OrgGroup Margin="0,20" HorizontalAlignment="Center"
DataContext="{Binding Children}" />
</StackPanel>
</Grid>
</UserControl>
<UserControl x:Class="OrgGroup">
<Grid>
<!-- this {Binding} to nothing means bind to DataContext}-->
<ItemsControl ItemsSource="{Binding}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:OrgBar Style="{StaticResource OrgBarStyle}"
DataContext="{Binding}" />
<!-- this {Binding} refers to the the child node this time} -->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
That's essentially a tree structure, so like Paully suggested, I would start with a TreeView (Silverlight Toolkit) and customize the control template and treeview itself.

Resources