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.
Related
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?
I have a tab control where the tabs are created at runtime. The contents of the tabs will be one of several user controls, each containing hundreds of other controls. As it takes a long time to create these user controls, I'm trying to find a way to reuse them rather than creating a new instance for each tab.
I'm setting the tab page contents using a DataTemplate as follows:
<DataTemplate>
<ScrollViewer Content="{Binding Content}" />
</DataTemplate>
Where Content is the view model for the view I want to show in the tab.
Elsewhere I use data templates to map each view model to a view, e.g.
<DataTemplate DataType="{x:Type vm:MyViewModel1}">
<ctl:CacheContentControl ContentType="{x:Type ctl:MyView1}" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MyViewModel2}">
<ctl:CacheContentControl ContentType="{x:Type ctl:MyView2}" />
</DataTemplate>
CacheContentControl is a wrapper around ContentControl that I use for the caching:
public class CacheContentControl : ContentControl
{
private static Dictionary<Type, Control> cache = new Dictionary<Type, Control>();
public CacheContentControl()
{
Unloaded += CacheContentControl_Unloaded;
}
private void CacheContentControl_Unloaded(object sender, RoutedEventArgs e)
{
Content = null;
}
private Type _contentType;
public Type ContentType
{
get { return _contentType; }
set
{
_contentType = value;
Content = GetView(_contentType);
}
}
public Control GetView(Type type)
{
if (!cache.ContainsKey(type))
{
cache.Add(type, (Control)Activator.CreateInstance(type));
}
return cache[type];
}
}
This ensures that the DataTemplate first checks the cache to see if it can reuse a control before creating a new instance.
This works for any new tabs that are created. The first tab is slow as expected since it needs to create the initial control, but all subsequent tabs using the same control load almost immediately. The problem I'm having is that when I click back to a previous tab the control no longer appears and the tab is blank.
I guess this is happening because I can't show the same control instance on the same form multiple times which makes sense. I've been able to work around it by handing the IsVisibleChanged event for my CacheContentControl as follows:
private void CacheContentControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (IsVisible && ContentType != null)
{
Control ctl = GetView(_contentType);
ctl.DataContext = DataContext;
Content = ctl;
}
else
{
Content = null;
}
}
When a tab loses focus it removes the control, the tab that receives focus can then retrieve the control from the cache which seems to work.
The issue is that the speeds have gone back to being slow again and I'm not sure why. Obviously it's possible to move a control instance from one tab to another without delay as it does this every time I create a new tab. There must be something the DataTemplate does differently to change the control's parent?
I’m not sure that you need to go so far as to cache your user controls as WPF largely does this for you.
The normal approach inside a DataTemplate keyed by type is not to use a ContentControl but simply to have the UserControl as the template.
<DataTemplate x:Key=“MyViewmodelType”>
<MyViewControl />
</DataTemplate>
The TabControl will have a ContentControl which has its Content set to whichever ViewModel is applicable and WPF will automatically render it using the specified DataTemplate for that type and set the DataContext.
I have built massive WPF Views that were rendered using DataTemplates as per the simpler approach outlined above without encountering any serious performance issues and I think that you could safely start developing by simply binding ContentControl.Content to your ViewModel instance and letting WPF handle the rendering using the simpler DataTemplate approach above.
I am trying to create a simple dialog-type control that is based on the WPF Window class (Popup won't do the trick here).
In my app I register a DataTemplate in Application.Resources:
<Application.Resources>
<DataTemplate DataType="{x:Type local:EntitySelectorViewModel}">
<local:EntitySelector></local:EntitySelector>
</DataTemplate>
</Application.Resources>
In my Window control I set Window.Content and I expect WPF will set the ContentTemplate to an instance of EntitySelector based on the DataTemplate registration shown above:
[Export(typeof(EntitySelectorDialog))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class EntitySelectorDialog : Window
{
[ImportingConstructor]
public EntitySelectorDialog(EntitySelectorViewModel vm)
{
InitializeComponent();
// DataContext = vm; // does not work
// EDIT: Per two answers shown below the following should work but it does not.
Content = vm;
}
}
The problem is that WPF does not resolve the ContentTemplate i.e. an instance of EntitySelector is not created. Furthermore, when I look at the XAML for EntitySelectorDialog I see that an instance of the shell has been injected
into the Window control (EntitySelectorDialog).
I don't know enough about Prism to know if I want to go with the flow and use the shell somehow or if I want to prevent Prism from injecting it at all. I don't think I have any need for it in this specific control so if it makes sense to just prevent Prism from injecting it I would prefer that route.
For the record I have tried removing the Prism attributes from my Window control and I new up the components manually. That appears to have no effect - Prism still manages to somehow inject the shell and my ContentTemplate is not resolved.
There is no XAML to show for the Window control (EntitySelectorDialog) except the Window declaration itself - I want the content to come entirely from the ContentTemplate (EntitySelector).
I've looked at this which may provide an answer but I don't know how to implement it without breaking the rest of the app:
Getting Unity to Resolve views in XAML
Set the Content of the window to a ContentControl and set or bind the Content property of this one to the view model:
[Export(typeof(EntitySelectorDialog))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class EntitySelectorDialog : Window
{
[ImportingConstructor]
public EntitySelectorDialog(EntitySelectorViewModel vm)
{
InitializeComponent();
DataContext = vm;
Content = new ContentControl() { Content = vm };
}
}
You need to set vm to EntitySelectorDialog.Content to trigger WPF solve the DataTemplate according to the type. So you either add
Content = vm;
in constructor or add
Content = {Bing}
in Xaml.
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.
I am using the Visifire charts to display data on a Windows Phone 7 application.
I created a chart that was properly bound to a dependency property. It worked great. I decided to make the chart into a user control, since I was going to use it in another project as well with the same setup. Now my databinding doesn't work unless I bind it in the code behind rather than in the XAML.
here's what I have:
<UserControl ... x:Name="root">
...
<chart:DataSeries ... DataSource="{Binding ElementName=root, Path=Results}">
...
</UserControl>
and the code behind:
public MyList Results
{
get { return (MyList)GetValue(ResultsProperty); }
set { SetValue(ResultsProperty, value); }
}
// Using a DependencyProperty as the backing store for Results. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ResultsProperty =
DependencyProperty.Register("Results", typeof(MyList), typeof(MyChart), new PropertyMetadata(null));
public GoogleChart()
{
Loaded += delegate
{
// theChart.Series[0].DataSource = Results;
};
Results = new GoogleResults();
InitializeComponent();
}
If I uncomment the line theChart.Series[0].DataSource = Results; it works perfectly. But if I leave that line commented (like I had before I moved the chart to the UserControl) it doesn't bind. (By the way: theChart is the x:name of the parent of the chart. So the first element, .Series[0], references to the chart).
Does anyone know why this would happen? Again, it worked great until I moved the code to a UserControl.
Thanks
If I'm understanding you correctly you've created this UserControl so that you can place instances of it into various pages in your app.
In that case you are likely to be giving those instances a name. That name will replace the name "Root" that is initially assigned in the UserControl's Xaml. Hence the binding for ElementName=Root will fail.
Typically there is a root element (normally a Grid) with the name "LayoutRoot". Hence instead of relying on the UserControl name which can change use "LayoutRoot" which by convention is the Content element for the UserControl. Like so:-
<chart:DataSeries ... DataSource="{Binding ElementName=LayoutRoot, Path=Parent.Results}">
Note the property path now starts with Parent which takes you up to the UserControl without actually needing to know the UserControl's name.