Creating new UIElements via commands - c#

Is there a simple way to do this?
My problem is mostly that the user triggers the element creation via a context menu on on control, and then wants to create a copy, or a new element nearby and the like.
I cannot seem to find a way to pass the appropriate information to the Execute function.
I know about CommandParameter but my gut feeling is to pass the entirety of the source control (or its parent in the case of the right click menu) and that seems wrong.
What is the idiomatic way to do this?

What level of duplication are you talking about?
The easiest case:
1) IEumerable<object> in your ViewModel, with different ViewModels(IFunnyControlViewModel, ISadCotrolViewModel), etc..
2) Create ItemsControl in View and bind against that collection. You can use DataTemplates to "map" different viewmodel to different view control.
3) Receive ViewModel Execute() with underlying ViewModel, just "re-add" it to the collection, thus "referencing" the same object, if you want to keep them synchroized, or clone it.
public YourViewModel {
public IEnumerable<YourBaseControlViewModel> Controls {get; set;}
public YourViewModel()
{
Controls = new List<YourBaseControlViewModel();
Controls.Add(new YourFunnyControlViewModel());
}
// Called from View by Command.
public void DuplicateControl(YourBaseControlViewModel Control)
{
// either duplicate it using cloning, or add the same reference.
Controls.Add(Control);
}
}
And within ItemsControl, something like:
<ItemsControl ItemsSource="{Binding Controls}">
<ItemsControl.Resources>
<DataTemplate x:Type="{x:Type blah:YourFunnyControlViewModel}">
custom stuff here
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>

Related

How to bind mdiContainer children to viewModel property?

I am using CodePlex wpfmdi container for my WPF application.
I need to bind MdiContainer's children to a viewModel property.
<mdi:MdiContainer Name="Container" Grid.Row="1" Background="GhostWhite" Children="{Binding Path=Container}"/>
If I do this I am getting this error:
Object of type 'System.Windows.Data.Binding' cannot be converted to type 'System.Collections.ObjectModel.ObservableCollection`1[WPF.MDI.MdiChild]'
This is what the Children property in MdiContainer looks like:
public ObservableCollection<MdiChild> Children { get; set; }
What am I doing wrong?
The Children property is not exposed as a dependency property, which means you cannot bind it. Furthermore, it is initialized once in the constructor of the MdiContainer type and then a handler is added to the CollectionChanged event of the underlying ObservableCollection<MdiChild>. It is never updated or removed.
Therefore, although the Children property has a setter, it will break the control if you use it to set a different collection. This also implies that you cannot simply create attached properties to expose a bindable Children dependency property.
Apart from that, MdiChild is a Control, so it actually contradicts the purpose of your view model. If you expose a collection of user interface controls from your view model this conflicts with the MVVM pattern. View models should not have any knowledge about the view. However, the MDI controls do not seem to follow the usual WPF practices for custom controls, so there is not much room for improvement here, data templating is not supported, the MdiContainer is a UserControl and there are very limited dependency properties.
If you really want to continue working with this control with your current approach, you could:
Create a custom attached behavior to synchronize your view model collection with the Children collection of the MdiContainer and vice-versa, see XAML behaviors in WPF.
Use the Loaded event to assign the Children collection to your view model property.
<mdi:MdiContainer Name="Container" Grid.Row="1" Background="GhostWhite" Loaded="MdiContainer_OnLoaded">
private void MdiContainer_OnLoaded(object sender, RoutedEventArgs e)
{
var mdiContainer = (MdiContainer)sender;
var dataContext = (Main)mdiContainer.DataContext;
if (dataContext == null)
return;
dataContext.Children = mdiContainer.Children;
}
Use an EventTrigger on the Loaded event with a custom trigger action that sets the Children collection. This is just a different variant of the previous approach that does not require code-behind.
The new XAML behaviors for WPF package, which replaces the legacy Blend behaviors from the System.Windows.Interactivity namespace already includes such a trigger action. Install the Microsoft.Xaml.Behaviors.Wpf NuGet package and use this:
<mdi:MdiContainer Name="Container" Grid.Row="1" Background="GhostWhite">
<behaviors:Interaction.Triggers>
<behaviors:EventTrigger EventName="Loaded">
<behaviors:ChangePropertyAction TargetObject="{Binding DataContext, ElementName=Container}"
PropertyName="Children"
Value="{Binding Children, ElementName=Container}"/>
</behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
</mdi:MdiContainer>
Note that with these approaches, you either synchronize to your own collection or you work directly with the collection of the MdiContainer that you passed to your view model. These are only workarounds. If you would want to implement this in a clean and MVVM compliant way, I think you would need to extend or fix the control itself, which is rather costly and not recommendable, since it seems to be dead anyway.

How to create dynamic forms in WPF using MVVM pattern?

I need to create a dynamic forms in WPF. For example, say I need to allow the user to create a family tree with information about each of the people and each person will have infomation about their occupation.
class Person
{
int age;
int dateOfBirth;
List<Job> jobList = new List<Job>();
}
class Job
{
string title;
int salary;
}
This program needs to be able to have fields that will allow data entry of all the members in the class, including multiple jobs. I would like the forms data to (possibly) coded in XAML, where if they click the button "Add Person" it will expand another person entry box to allow the user to add information about the person. This is the same as "Add Job" but the jobs are only added under that person.
(Note that the end result of this will be a tree data structure that contains all the people and their children)
How would I go about doing this in WPF using the MVVM pattern? Before I learned the MVVM pattern, I had used code behind and created dynamic view using c# to code the XAML view and added it dynamically as child elements. But I don't think that is the best way to do this since it seems too tedious.
I'm new to the MVVM pattern so some small code snippets of how I would do this (using databinding?) would be very helpful.
I made a quick example coded in XAML of how the form might look like:
This problem is solved in WPF using DataTemplates. In any place you need a "repeated" form, you will set up something like this:
<ItemsControl ItemsSource="{Binding Jobs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<...>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You use ListView to get a collection control, that will display a instance of the DataTemplate for every item in the bound collection. Inside of the DataTemplate, your DataContext is the bound item inside the collection, ie. an instance of the Job class.
If Jobs was an ObservableCollection<T> then the control would automatically update when items were added or removed from the bound collection.

How to let caliburn.micro know to reattach actions when cal:Bind.Model is pointed to new a object instance?

I have a simple use case which I am struggling with in Caliburn.Micro. I can get this to work easily with traditional bindings, but I'd like to use the framework properly.
In short, this is an MDI style app with a single top level toolbar of which I'd like to bind the context to the Conductor.ActiveItem. Basically, the issue I'm seeing is that Calibun set up the Actions for the toolbar buttons for the first opened tab, but later when ActiveItem is changed, the connected actions continue to point to the first assigned ActiveItem and not the new one.
My main ViewModel is of type Conductor.Collection.OneActive.
public sealed class MainViewModel : Conductor<ITabPage>.Collection.OneActive
{
}
This view model contains a simple list of tabs each with public methods Save() and Undo() (along with bool property implementations for CanSave and CanUndo).
public interface ITabPage : IScreen, IDisposable
{
void Save();
void Undo();
bool CanSave { get; }
bool CanUndo { get; }
}
Now the view contains the top-level toolbar with buttons invoking the actions on the ActiveItem and a TabControl to display the conductor items.
<Window xmlns:cal="http://www.caliburnproject.org" ...>
<DockPanel>
<ToolBar DockPanel.Dock="Top" cal:Bind.Model="{Binding ActiveItem}">
<Button Name="Save">Save</Button>
<Button Name="Undo">Undo</Button>
</ToolBar>
<TabControl x:Name="Items">
</TabControl>
</DockPanel>
</Window>
Using normal binding and ICommands works fine, but I'd like to not fight the framework on this. Is there something I'm missing or misusing with cal:Bind.Model? Or perhaps a way to let it know to refresh? I've also tried calling Refresh() when ActiveItem is changed and I'm *absolutely" sure the CanSave and CanUndo are notifying properly (I've set break points and I've had success with normal bindings.)
Found a solution: I was misusing caliburn:Bind.Model.
The correct bind type is
caliburn:Bind.ModelWithoutContext="{Binding ... }"
Using that dependency property helper instead allows the Actions to be routed correctly to the ActiveItem as it changes.

Getting parent of new tab after adding to bound TabControl (mvvm)

I'm adding a close button to my tabs using the following guide:
http://www.codeproject.com/Articles/84213/How-to-add-a-Close-button-to-a-WPF-TabItem
This has become a problem because the event uses the 'parent' of the added tab to remove that tab from the tabcontrol. I'm binding the tab control using mvvm, so the parent property is apparently not being set and giving me a null reference exception for the parent when the event tries to remove from it.
Here's the binding so you get the idea:
<TabControl Name="tabControl" Margin="0,22,0.2,-5.2" ItemsSource="{Binding Tabs}" Background="#FF4C76B2"/>
Heres where the tabs are being added.
private void AddTab(object tabName)
{
ClosableTab newTab = new ClosableTab();
newTab.Title = "title?";
//newTab.Header = tabName;
TextBox test = new TextBox();
test.Text = "CONTENT (" + tabName + ") GOES HERE";
newTab.Content = test;
Tabs.Add(newTab);
OnPropertyChanged("Tabs");
}
Here is the event where the null reference is taking place:
void button_close_Click(object sender, RoutedEventArgs e)
{
((TabControl)this.Parent).Items.Remove(this);
}
As I see it there are two options:
try to find another way to remove the tab (without the parent
property)
try to find a way to somehow set the parent property (which cant be
done directly, it throws a compiler error)
That doesn't sound like MVVM to me. We work with data, not UI elements. We work with collections of classes that contain all of the properties required to fulfil some requirement and data bind those properties to the UI controls in DataTemplates. In this way, we add UI controls by adding data items into these collections and let the wonderful WPF templating system take care of the UI.
For example, you have a TabControl that we want to add or remove TabItems from... in a proper MVVM way. First, we need a collection of items that can represent each TabItem:
public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<string>), typeof(TestView));
public ObservableCollection<string> Items
{
get { return (ObservableCollection<string>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
I'm just using a DependencyProperty because I knocked this up in a UserControl and I'm just using a collection of strings for simplicity. You'll need to create a class that contains all of the data required for the whole TabItem content. Next, let's see the TabControl:
<TabControl ItemsSource="{Binding Items}" ItemTemplate="{StaticResource ItemTemplate}" />
We data bind the collection to the TabControl.ItemsSource property and we set the TabControl.ItemTemplate to a Resource named ItemTemplate. Let's see that now:
xmlns:System="clr-namespace:System;assembly=mscorlib"
...
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type System:String}">
<TabItem Header="{Binding}" />
</DataTemplate>
This DataTemplate defines what each item in our collection will look like. For simplicity's sake, our strings are just data bound to the TabItem.Header property. This means that for each item we add into the collection, we'll now get a new TabItem with its Header property set to the value of the string:
Items.Add("Tab 1");
Items.Add("Tab 2");
Items.Add("Tab 3");
Note that I included the System XML Namespace Prefix for completeness, but you won't need that because your DataType will be your own custom class. You'll need more DataTemplates too. For example, if your custom class had a Header property and a Content property, which was another custom class, let's say called Content, that contained all of the properties for the TabItem.Content property, you could do this:
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type YourPrefix:YourClass}">
<TabItem Header="{Binding Header}" Content="{Binding Content}" />
</DataTemplate>
<DataTemplate DataType="{x:Type YourPrefix:Content}">
<YourPrefix:SomeUserControl DataContext="{Binding}" />
</DataTemplate>
So this would give you TabItems with Headers set and Content that comes from SomeUserControl which you could design. You don't need to use UserControls, you could just add more UI controls to either DataTemplate. But you will need to add more controls somewhere... and more classes and properties, always remembering to correctly implement the essential INotifyPropertyChanged interface.
And finally, to answer your question in the proper MVVM way... to remove a TabItem, you simply remove the item that relates to that TabItem from the collection. Simple... or it would have been if you really had been using MVVM like you claim. It's really worth learning MVVM properly as you'll soon see the benefits. I'll leave you to find your own tutorials as there are many to chose from.
UPDATE >>>
Your event handling is still not so MVVM... you don't need to pass a reference of any view model anywhere. The MVVM way is to use commands in the view model. In particular, you should investigate the RelayCommand. I have my own version, but these commands enable us to perform actions from data bound Buttons and other UI controls using methods or inline delegates in the view model (where action and canExecute in this example are the CommandParameter values):
<Button Content="Close Tab" Command="{Binding CloseTabCommand}"
CommandParameter="{Binding}" />
...
public ICommand CloseTabCommand
{
get { return new ActionCommand(action => Items.Remove(action),
canExecute => canExecute != null && Items.Contains(canExecute)); }
}
So whatever view model has your Tabs collection should have an AddTabCommand and a CloseTabCommand that add and remove items from the Tabs collection. But just to be clear, for this to work properly, your ClosableTab class should be a data class and not a UI control class. Use a DataTemplate to specify it if it is a UI control.
You can find out about the RelayCommand from this article on MSDN.

Binding properties of multiple tree views to the same ViewModel

I am working on a MVVM implementation, where i'll spawn multiple views (side by side) each containing a tree control.
each of the views will have a similar tree, with a copy of [almost] all the same items.
I would like to synchronize the IsExpanded property on all the view/TreeView's..
meaning, if i collapse one node, i would like all of them to collapse (and some goes for column widths etc).
One way to do this, would be to bind all views to the same viewmodel, and have a DependencyProperty on that ViewModel, and set up the binding as Two Way on each view. However, i need each view to be bound to a separate viewmodel so that it can display unique values. I just need to synchronize a few properties of the tree, such as IsExpanded and Width.
What would be the best approach here?
You can use Prism and EventAggregator service from it to exchange data between view models.
There's no reason you can't have different collections within a single ViewModel, if that is the best design option. Especially if your multiple Trees / Collections are filtered from some 'complete set'; it might actually make more sense.
Just add multiple collections to your ViewModel, and bind to them.
public class MyViewModel : INotifyPropertyChanged
{
public ObservableCollection<MyItem> FirstTreeCollection
{
get
{
// whatever you need to do here
}
}
public ObservableCollection<MyItem> SecondTreeCollection
{
get { /* etc */ }
set { /* etc */ }
}
// etc
public bool Collapsed
{
get;
set;
}
}
and your Views should bind accordingly
// in your first view that contains a tree
<UserControl x:Class="View1" ...>
<TreeView Name="FirstTree"
ItemsSource={Binding FirstTreeCollection}
Collapsed={Binding Collapsed} ... >
// & in your second view that contains a tree
<UserControl x:Class="View2" ...>
<TreeView Name="SecondTree"
ItemsSource={Binding SecondTreeCollection}
Collapsed={Binding Collapsed} ... >
To clarify, I'm suggesting that you use a single ViewModel for all of these Tree-containing Views.
The ViewModel won't need a DependencyPropery--it will just need to expose a property that implements INotifyPropertyChanged.
The two ViewModels will need to have some way of sharing state, and exposing a property that represents that state. How you share the state depends heavily on how your ViewModels are instantiated (and probably other factors). For example, if your two VMs are being instantiated by some parent object, the parent may create one instance and pass it to both VMs in their constructors.
If you display the treeview's using xaml, you can bind every treeview to the first treeview spawned.
For example you can use some binding like this:
<TreeView Name="FirstTreeView" />
<TreeView Name="SecondTree"
IsExpended = {Binding Path=IsExpanded, ElementName=FirstTreeView, Mode=TwoWay}/>

Categories

Resources