Assign DataTemplate to view with view model in code behind - c#

I'm trying to set a new DataTemplate as a new Window resource in my MainWindow derived from the System.Windows.Window class. The code for the XAML is quite simple and looks like this:
<Window.Resources>
<DataTemplate DataType="{x:Type model:MyViewModel}">
<view:MyView />
</DataTemplate>
</Window.Resources>
What I exactly do here?
I try to show my data (MyViewModel) in or as a specific view (MyView). So far I do understand. Otherwise I wouldn't see the form itself, but the view model as a string with my.namespace.MyViewModel in the window.
But programmatically I do not understand, how to achieve the same. I know, that I have to add a new DataTemplate to the resources of my window. For this I have to "tell" the DataTemplate, which view to use (for the representation) and which data I want to represent, right?
So it must be something with:
DataTemplate template = new DataTemplate();
template.DataType = typeof(MyViewModel);
// Something, something ...
this.Resources.add(...);
Is this the right way to go? Or am I completely wrong?
I searched the web for solutions and also my WPF book, but there are only XAML implementations.
Why do I do that?
I have a headered content control which loads view models dynamically. The problem here is, that the user controls are sometimes dynamic and in case of data presentation I need to assign a specific (dynamic created) view to the data. So I try to load the current static user controls also in the way shown above.
Is there a way to go?
Or is there a better way to achieve the same results?

Related

WPF and MVVM: bind UserControl to XAML dynamically

seems like a trivial task: i am building a wpf application, using MVVM pattern. what i want is dynamically change part of a view, using different UserControls, dependent on user input.
let's say, i have got 2 UserControls, one with a button, and another with a label.
in main view i have a container for that. following XAML "works":
<GroupBox Header="container" >
<local:UserControlButton />
</GroupBox>
and a UserControl element with buttons pops up. if i change it to another one, it works too.
question is how to feed that groupbox dynamically. if i put something like that in my model view:
private UserControl _myControl;
public UserControl MyControl
{
get
{
return _myControl;
}
set
{
_myControl= value;
InvokePropertyChanged("MyControl");
}
}
and change my view XAML to something like:
<GroupBox Header="container" >
<ItemsControl ItemsSource="{Binding MyControl}" />
</GroupBox>
and feed it from command with usercontrol for button or for label: nothing happens, although "MyControl" variable is set and is "invoke property changed"..
Obviously there are many ways to skin this particular cat - but to answer the question of why it doesn't work you need to look into the ItemsSource property of ItemsControl on MSDN.
The items control is designed to show multiple items, provided through an IEnumerable passed to the ItemsSource property. You are passing a UserControl, so the binding will fail.
For your example, I would change the ItemsControl to a ContentControl and bind the content to your MyControl property. This should then work.
<GroupBox Header="container" >
<ContentControl Content="{Binding MyControl}" />
</GroupBox>
However, I would strongly recommend looking into other ways of doing this - having a control in your VM breaks MVVM to my mind. Depending on what you are doing look at data templates - #Sheridan's link in the comments provides an great description of a way to do it.
Couldn't post this as a comment so adding as answer..
Have a look at this:
Implementing an own "Factory" for reusing Views in WPF
It uses DataTemplates but doesn't require the DataTemplate section for each view. If you potentially have a lot of user controls/views you wish to display or you are reusing through multiple views or you are intending to actually dynamically generate a view (versus just loading an existing user control) then this might suite your needs.

Programmatically setting the content of a ContentControl in WPF

I have a UserControl named "WorkspaceView" and its only purpose is to show other views as tabs. Call these views ViewA, ViewB etc. Which of these views to present should be determined on runtime, so I figured I needed a control that can present ... well ..stuff.
ContentControl to the rescue. Except ... I can't make it work. I'm trying to new up a usercontrol of type ViewA in the code behind and assign it to my MyContent, which is the ContentControl. I've tried:
public WorkspaceView()
{
InitializeComponent();
DataContext = new View(A); //Hoping that the DataContext will propagate down
}
Second attempt was
public WorkspaceView()
{
InitializeComponent();
var binding = new Binding {Source = new ViewA()};
MyContent.SetBinding(ContentControl.ContentProperty, binding);
}
In both cases, I see an empty box, but since I've hard wired a TextBlock into ViewA, I'd expect it to show me that text. What am I doing wrong?
Despite knowing that MVVM is the preferred way to develop WPF applications, I'd prefer to see how I can do this with code behind files. Later on, I will redo the application with MVVM, but first I need to get some basic understanding of WPF.
In response to the suggestions so far, I've tried
MyContent.Content = new ViewA();
but still I the text that is in ViewA does not appear. I've also at the bottom of this post included a screenshot of what the application renders.
WorkspaceView
Resource file
What is rendered
Have you tried simply doing this?
MyContent.Content = new ViewA();
EDIT
Try simplifying your code a bit and working from there. For instance:
public WorkspaceView()
{
InitializeComponent();
// Something better than UserControl should be used here
ObservableCollection<UserControl> views = new ObservableCollection<UserControl>();
views.Add(new ViewA());
views.Add(new ViewB());
DataContext = views;
}
<Border ..>
<TabControl x:Name="TabControl"
..
ItemsSource="{Binding}" />
</Border>
This code sets a WorkspaceView.DataContext to a collection of UserControls. When you specify {Binding} whithin WorkspaceView's XAML you are refering to the whole DataContext object (i.e. your collection.) This way you are setting the TabControl.ItemsSource to your collection of views.
Now you could create DataTemplates targeting the type of each view to control how each control is displayed in its tab within the TabControl.

How to create DataTemplate from UserControl instance?

Question
Basically I would like to do the following but it seems that I cannot:
UserControl myControl = new UserControl();
DataTemplate template = new DataTemplate(myControl);
The question: Is it possible to construct a DataTemplate from UserControl instance? If not, are there any other possible solutions?
Real problem
I'm working on a project where majority of UI views are simple static Word-like documents (e.g some text fields and maybe some images, nothing too fancy). Because most of persons working on this project are not coders we have designed very simple in-house markup language for UI generation. An example of markup of simple view is following:
First name: [Person.FirstName]
Last name: [Person.LastName]
Address: [Person.Address.Street], [Person.Address.City]
Now these templates are loaded at runtime and usercontrols are created based on them. In this case one usercontrol would be created and it would contain simply couple of stack panels and text blocks so that resulting control would look a bit like text document. XAML equivalent would be something like:
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="First name: "/>
<TextBlock Text={Binding Person.FirstName}
</StackPanel>
<StackPanel>
...
</StackPanel>
...
</StackPanel>
Then, I started to implement support for lists but couldn't think of a way how to do that. In theory it is simple and I came up with following syntax (+ XAML equivalent):
[List Customers]
First name: [Person.FirstName]
Last name: [Person.LastName]
Address: [Person.Address.Street], [Person.Address.City]
[EndList]
->
<StackPanel>
<ItemsControl ItemsSource="{Binding Customers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
[Insert code from previous XAML example here]
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
But I can't do that because it seems that I cannot construct DataTemplate directly from UserControl instance. There would be no problems if my UserControls were types, but they are not.
One possible solution is that I could bind ItemsControl.Items directly to list of UserControls (instead of binding to ItemsSource and ItemTemplate) but this is sub-optimal in our case for couple of reasons. I would be willing to try some other solutions first!
Update
For clarification: all I have is plain instance of UserControl class which contains the content I need. E.g.
UserControl control = new UserControl();
var panel = new StackPanel();
panel.Children.Add(...);
panel.Children.Add(...);
control.Content = panel;
// How to use that control as ItemTemplate for ItemsControl?
// It seems that it is not possible directly but I want to
// know what my options are.
I don't have class for it because I'm constructing it at run-time and I don't want to create new type dynamically by emiting IL code because it is way too painful.
Creating a datatemplate from Code behind goes like this:
FrameworkElementFactory factory = new FrameworkElementFactory(MyUserControl.GetType());
DataTemplate dt = new DataTemplate();
dt.VisualTree = factory;
yourItemsControlInstance.ItemTemplate = dt;
A datatemplate is a definition of controls to be built at runtime, that is way this construction with a ElementFactory. You do not want the same instance of the UserControl for every item in your ItemsControl.
Ah I understand your problem now. I don't think there is an easy way (one or two lines of code) to create a datatemplate from a UserControl instance.
But to solve your problem I see two directions:
At the point where an usercontrol is created, create a datatemplate instead and use that. It will be cumbersome, with nested FrameworkElementFactories. I have never done that, and the MSDN documentation says that you may encounter some limitations you cannnot do compared to datatemplates in Xaml. But if it is simple it must be doable. There used to be a codeproject article by Sacha Barber you could use as a guidance (if needed).
You pack the creation of the UserControl in a method called private UserControl createMyUserControl(){}
And do something like this:
ItemsControl itemsControl = new ItemsControl();
foreach (var customer in Customers)
{
var usercontrol = createMyUserControl(...);
usercontrol.DataContext = customer;
itemsControl.Items.Add(usercontrol);
}
Second option is less elegant in my opinion, so I would check out the option 1 first.
WPF: How to create Styles in code/and magical Content (see section at the end for extensive sample of a DataTemplate in Code behind)
I think you can replace UserControl with the ContentControl.
Just set the content of the ContentControl to the desired template and use it as ItemTemplate for the ItemsControl.

Loading XAML at runtime using the MVVM pattern in WPF

This is a question that extends from the originally posted here:
Link to loading-xaml through runtime
I'm working on a WPF MVVM application that loads XAML content dynamically from an external source, very similar as the answer in the post above.
Here is what I got so far:
My View declares an instance of the ViewModel as a resource and creates an instance of that ViewModel
In my ViewModel constructor I'm loading a XamlString property coming from an external source (file or db..)
In my view I have a button that user clicks after ViewModel finishes loading and in the click-event code-behind I'm deserializing the dynamically loaded XAML and add it to my grid.
My question is, how can I eliminate code-behind and automate the logic so the View can render the new xaml section dynamically right after the ViewModel is done getting the XAML content and initializing the string property?
Should I use some kind of Messaging Bus so the ViewModel notifies once the property has been set so the View can add the new content?
What troubles me is the fact that ViewModels do have a reference to Views and should not be in charge of generating UI elements.
Thanks in advance!
Edit:
Just to clarify: in my particular case I am not trying to bind a Business Object or Collection (Model) to a UI element (e.g. Grid) which obviously could be accomplished through templates and binding. My ViewModel is retrieving a whole XAML Form from an external source and setting it as a string property available to the View. My question is: Who should be in charge of deserializing this XAML string property into a UI element and add it programmatically to the my grid once my Xaml string property in the VM is set?
This sounds to me more of like a View responsibility, not ViewModel. But the pattern as i understand it enforces to replace any code-behind logic with V-VM bindings.
I have a working solution now and I'd like to share it. Unfortunately I did not get rid of code-behind completely but it works as I expect it to. Here is how it works(simplified):
I have my simplified ViewModel:
public class MyViewModel : ViewModelBase
{
//This property implements INPC and triggers notification on Set
public string XamlViewData {get;set;}
public ViewModel()
{
GetXamlFormData();
}
//Gets the XAML Form from an external source (e.g. Database, File System)
public void GetXamlFormData()
{
//Set the Xaml String property
XamlViewData = //Logic to get XAML string from external source
}
}
Now my View:
<UserControl.Resources>
<ViewModel:MyViewModel x:Key="Model"></ViewModel:MyViewModel>
</UserControl.Resources>
<Grid DataContext="{StaticResource Model}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel>
<!-- This is the Grid used as a Place Holder to populate the dynamic content!-->
<Grid x:Name="content" Grid.Row="1" Margin="2"/>
<!-- Then create a Hidden TextBlock bound to my XamlString property. Right after binding happens I will trigger an event handled in the code-behind -->
<TextBlock Name="tb_XamlString" Text="{Binding Path=XamlViewData, Mode=TwoWay, UpdateSourceTrigger=LostFocus, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Visibility="Hidden" Loaded="tb_XamlString_Loaded" />
</StackPanel>
</Grid>
Basically I created a hidden TextBlock bound to my XAML String property in the ViewModel and I hooked its Loaded event to an event handler in the code behind of the View:
private void tb_XamlString_Loaded(object sender, RoutedEventArgs routedEventArgs)
{
//First get the ViewModel from DataContext
MyViewModel vm = content.DataContext as MyViewModel;
FrameworkElement rootObject = XamlReader.Parse(vm.XamlViewData) as FrameworkElement;
//Add the XAML portion to the Grid content to render the XAML form dynamically!
content.Children.Add(rootObject);
}
This may not be the most elegant but gets the job done. Like some people say, in MVVM there are some cases like this where little code-behind code is needed. It doesn't hurt and also part of this solution still uses the V-VM Binding principles when using the VM to retrieve and populate the XamlString property and exposing it to the View. If we would like to Unit Test the XAML parsing and loading functionality we could delegate it to a separate class.
I hope someone finds this useful!
I'm having trouble understanding what you're saying, so my answer will be based on my interpretation. You should consider posting a sample (simplified) of what you're trying to do.
1) I think you're misunderstanding what MVVM does. MVVM is mostly a binding-based pattern. Your view model should be exposing properties containing business objects and your view should just be binding to those properties. If I am misunderstanding you, and that's what you are doing, then your problem is that your view needs to be aware of when the properties get updated (after you deserialize your xaml, etc). There are two ways to do this: INotifyPropertyChanged interface on your viewmodel, or make your view model inherit from DependencyObject, and make the properties dependency properties. I won't go into details here, because this is a large subject that you should research on Google before making a decision.
2) Generally speaking, you shouldn't use click events inside your view if you're using MVVM. Instead, create properties on the view model of type ICommand (and create ICommand implementations to match, or use an implementation of DelegateCommand (google it) which will allow you to use delegates to implement the interface. The idea is, your view binds to the property and executes the handler directly inside the viewmodel.
3) If you want to push information from the viewmodel to the view, then you should create an event on the viewmodel and subscribe to it in the view, but this is a last resort, only to be used in cases like displaying a new window, etc. Generally, you should be using binding.
4) To be more specific about what you're doing, you should be binding your Grid's ItemsSource property to some property on the view model. Note, the property on the view model should be of type ObservableCollection<T> if you want to be able to add items and get instant updates.
Hope this helps.

Changing content of Window (WPF)

I've created a simple WPF application which has two Windows. The user fills in some information on the first Window and then clicks Ok which will take them to the second Window. This is working fine but I'm trying to incorporate both Windows into a single Window so just the content changes.
I managed to find this Resource management when changing window content which seems like it is what I'm after. However, I've search for ContentPresenter but couldn't find much help for how I need to use it. For example, if I use a ContentPresenter, where do I put the existing XAML elements that are in the two Windows? I'm guessing the first Window will go into the ContentPresenter but the second one will need to be put somewhere for when it needs to be switched in.
Any help would be great. A simple working example would be even better.
TIA
A ContentPresenter is normally used when restyling existing controls. It is the place where the Content of a control is placed. Instead you should use a ContentControl, which is simply a control that has a content element. Alternatively, you could directly set the Content of your window.
You extract the contents of your two existing windows into two UserControls. Then you create a new Window which will host the contents. Depending on your business logic, you set the content of that window (or that window's ContentControl if you want additional "master" content) to either of those two UserControls.
EDIT:
As a starting point. This is not complete working code, just to get you started. Note that this is bad architecture; you should probably use a MVVM or similar approach once you get this running!
<Window>
<ContentControl Name="ContentHolder" />
</Window>
<UserControl x:Class="MyFirstUserControl" /> <!-- Originally the first window -->
<UserControl x:Class="MySecondUserControl" /> <!-- Originally the second window -->
In code behind of Window:
// Somewhere, ex. in constructor
this.ContentHolder.Content = new MyFirstUserControl;
// Somewhere else, ex. in reaction to user interaction
this.ContentHolder.Content = new MySecondUserControl;
I use ContentPresenter for snapping in content. In the window, I put something like this:
<ContentPresenter Content="{Binding MainContent}" />
In the view model, I have a property called MainContent of type object:
public object MainContent { get { return (object)GetValue(MainContentProperty); } set { SetValue(MainContentProperty, value); } }
public static readonly DependencyProperty MainContentProperty = DependencyProperty.Register("MainContent", typeof(object), typeof(SomeViewModel), new FrameworkPropertyMetadata(null));
Whatever you set MainContent to will show up in the window.
To keep the separation between view and view model, I typically set the MainContent property to another view model and use a data template to map that view model to a view:
<DataTemplate DataType="{x:Type viewmodels:PlanViewModel}">
<views:PlanView />
</DataTemplate>
I put that data template in some central resource dictionary along with a bunch of other view-model-to-view mappers.

Categories