In WinForms it was relatively easy to swap out Panels at runtime for other panels. In WPF this seems to be rather more complex (especially from XAML).
Can anyone provide clear guidance on the 'best practice' way of swapping gui elements at runtime (think pages in a wizard type situation).
Many thanks.
This can be approached in XAML using datatemplates and/or triggers. For example, if each page in your wizard were represented in an underlying model as a separate class or object, you could use one of the following two options... Both use a ContentControl, which is the perfect control for when the content will vary greatly between different views of the same data.
Please note that the bindings are intended as pseudocode examples, just to convey intent!
DataTemplate-based, using different classes for each page:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type WizardPageOne}">
<!-- page 1 layout here -->
</DataTemplate>
<DataTemplate DataType="{x:Type WizardPageTwo}">
<!-- page 2 layout here -->
</DataTemplate>
<!-- ... etc -->
</Grid.Resources>
<ContentControl Content="{Binding CurrentPageModel, Source=Wizardmodel}" />
</Grid>
Or Trigger based, using a property that indicates the current page:
<ContentControl Content="{Binding WizardModel}">
<ContentControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentPageIndex} Value="1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!-- page 1 layout here -->
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding CurrentPageIndex} Value="2">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!-- page 2 layout here -->
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!-- .... etc -->
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Both options will only load the control for each page as it's required, so you don't have all of the controls "loaded but hidden" in the window.
The underlying concepts of WinFomrs and WPF is different. In WPF it is not advisable to play around with UIElements(Controls) directly. Make use of DataBinding/DataContexts and just operate on the data and then the UI will function accordingly. This concept is all about WPF MVVM pattern. You can look in to some MVVM samples and try it before doing more complex WPF projects.
A simple example, Suppose you need to dynamically disply a number of items in a ListBox, The typical winform way to do this is to create Items and add directly to the ListBox. But in WPF you create an ObservableCollection<Customer> and bind that to the ListBox.ItemsSource. then define a DataTemplate for Customer Data Type, this ensure the WPF system to understand how a Collection of Customers being displayed in the application. So when you add a new customer instance to the collection, magically your ListBox will get updated with one more item. Seems pretty straight forward and a very loosely coupled way of Data and View right?.
Best wishes on your WPF learning. -
http://www.bing.com/search?q=WPF+MVVM
So the high level clue to your question is, make the View appropriately for the Data and when Data/Property Change happens, WPF will take care of changing the Panels/Controls. So it is really simple than WinForms way when you approach from the Data and View perceptive.
A couple options come to mind. If you create your components as UserControls, and make use of Data Binding, then you should be able to do what you need with minimal fuss.
Option one is to load each component into your parent container (grid, canvas, whatever) with Visibility="Collapsed", and then show and hide them as needed. This has the advantage that you can do this declaratively in XAML.
The other option is to load the components as you need them, so in the event handler of a button, or some other UI element. In this case you would probably want to remove the current displaying item from the Children collection of your host component, and then instantiate your next control, set the DataContext (this is why binding is important), and add it to the Children collection.
(disclaimer: this is based on my experience doing basically what you are asking in Silverlight 3.0, so there may be some WPF quirks I am unaware of).
The MVVM suggestions here are all good. But if you're designing a page-oriented UI that needs to be navigable, you can use Structured Navigation, too.
I got no idea if this is considered good practice, but what we did on one of our project is quite simple. We defined panels that were all on top of each other and would simply set the visibility to either hidden or visible when it was needed.
Related
I am attempting to style every DataRecordPresenter in a XamDataGrid according to a VisualBrush that should flex according to the presenter in question. Specifically I'm aiming to highlight some rows, where the highlighting pattern is potentially complex (i.e. arbitrarily large, albeit in practice probably not more than 5-6 colours).
The solution I'm hoping to use looks something like this:
<!-- idp bound to namespace for Infragistics DataPresenters -->
<idp:XamDataGrid>
<idp:XamDataGrid.Resources>
<Style TargetType={x:Type idp:DataRecordPresenter>
<Style.Setters>
<!-- THE RELATIVESOURCE FAILS HERE -->
<Setter Property="Tag">
<Grid DataContext={Binding RelativeSource={RelativeSource Mode=FindAncestor,TargetType={x:Type idp:DataRecordPresenter}}}>
<!-- Content that relies on properties of the DataRecordPresenter -->
</Grid>
</Setter>
<Setter Property="Background">
<!-- THE RELATIVESOURCE WORKS HERE -->
<VisualBrush
ViewPort="12,12,12,12"
Visual="{Binding Tag,RelativeSource={RelativeSource Mode=FindAncestor,TargetType={x:Type idp:DataRecordPresenter}}"
>
</VisualBrush>
</Setter>
</Style.Setters>
</Style>
</idp:XamDataGrid.Resources>
</idp:XamDataGrid>
The issue is that whilst I am able to identify the DataRecordPresenter when constructing the actual VisualBrush (tested using Snoop - as intended the result is the contents of the Tag property), whilst trying to find the same object via the same mechanism from the context of the setter for the Tag property, I fail to identify any such ancestor.
I suspect this is because the Tag property is not associated with the visual (or logical) trees, whereas Background is, but I haven't managed to come up with a solution as yet that addresses the issue. I'd equally be happy to move the Grid into the VisualBrush, but I believe a VisualBrush is also detached from the relevant trees, so I don't think that'll work, or at least not with a simple inline definition.
I have a style in application resources which I want to apply to many different pie charts. The style looks like this:
<Style x:Key="aaa" TargetType="{x:Type nm:CustomChartControl}">
<Setter Property="..." Value="..." />
<!-- etc -->
<nm:CustomChartControl.Series>
<nm:PieSeries /> <!-- PROBLEM -->
</nm:CustomChartControl.Series>
</Style>
There is a lot more properties which I exluded for simplicity. This all works well. Now, some of my pies need to have a different "model" for paitning background for a slice (ex dashed), and this is were the problem occurs.
When I set a model (at runtime) for nm:PieSeries in a particular chart, then this model is also applied to all other pies that are shown in application. As if there is only one instance of that is used by all pies that applied the style.
Is there some way I can tell it to create a new instance of nm:PieSeries each time a Style is applied to new control?
You might try creating the PieSeries as a separate, non-shared resource:
<nm:PieSeries x:Shared="False" x:Key="NonSharedPieSeries" />
And then use that resource in the style:
Value="{Binding Source={StaticResource NonSharedPieSeries}}"
(...and thanks OP for correcting my error in how to bind it to Value).
I've got a ContentControl which has a style containing a border and other visual decorations. I want these decorations to disappear when the content is collapsed, so I figured I have to set the visibility of the ContentControl to collapsed in this case. I got this style for my ContentControl decoration:
<Style x:Key="DecoratedItem1" TargetType="{x:Type c:DecoratedItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:DecoratedItem}">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Black" BorderThickness="2" CornerRadius="2">
<StackPanel Orientation="Horizontal">
<Image Source="/Images/file.png"/>
<ContentPresenter Name="wContent"/>
</StackPanel>
</Border>
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=wContent, Path=Content.Visibility}" Value="Collapsed">
<DataTrigger.Setters>
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger.Setters>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The DecoratedItem class is just a subclass of ContentControl with additional DependencyProperties which are not relevant to this issue, I just wanted to note that I already have a subclass to which I could add code, if necessary.
This works when the content of the ContentControl is a UIElement, however if the content is generated by a DataTemplate it complains about not being able to find the Visibility property.
<!-- works -->
<c:DecoratedItem Style="{StaticResource DecoratedItem1}">
<TextBlock Text="ABC" Visibility="Collapsed"/>
</c:DecoratedItem>
<!-- doesn't work -->
<c:DecoratedItem Style="{StaticResource DecoratedItem1}" Content="ABC">
<c:DecoratedItem.Resources>
<DataTemplate DataType="{x:Type clr:String}">
<TextBlock Text="{Binding}" Visibility="Collapsed"/>
</DataTemplate>
</c:DecoratedItem.Resources>
</c:DecoratedItem>
The error for the second case diplayed in the debug output window is:
System.Windows.Data Error: 40 : BindingExpression path error:
'Visibility' property not found on 'object' ''String' (HashCode=-885832486)'.
BindingExpression:Path=Content.Visibility;
DataItem='ContentPresenter' (Name='wContent');
target element is 'DecoratedItem' (Name='');
target property is 'NoTarget' (type 'Object')
I understand why this happens, but don't know how to fix my style to work as I want it. I don't mind adding helper code to the DecoratedItem subclass if necessary. Any idea how to fix this?
[edit 1]
Some more explanation in regard to the proposed answer:
I can't enforce that the Content is always an UIElement. This is a model-view design after all, and of course I simplified the example a lot. In the real project the content is a model selected from the DataContext, which can be of several different types, and the DataTemplate builds a presentation for that model. Some of the DataTemplates decide (depending on model-state) that there is nothing to present and switch Visibility to Collapsed. I would like to propagate that information to the decorating container. The example above really just presents the problem and not the motivation, sorry.
[edit 2]
Not sure how knowing more about the model would help the problem, but here we go. The data in the Content field doesn't have much in common since it can be a lot of things, this DecoratedItem is supposed to be reusable to give a common visual style to items shown on some forms. Content can be stuff like work items whose DataTemplate collapses them if they are disabled; other kinds of Content can be incomplete and get collapsed. Of course other kinds never may get collapsed.
But note that the data model doesn't really have much to do with the question, which still is how to bind against the Visibility of the expanded content element (after possibly exposing it through the subclass in a bindable way).
There are a couple of ways of describing what's wrong. In the first, working example:
<c:DecoratedItem Style="{StaticResource DecoratedItem1}">
<TextBlock Text="ABC" Visibility="Collapsed"/>
</c:DecoratedItem>
the Content property of the ContentControl is set to be a TextBlock, which is a UIElement with a Visibility property. (This assumes that you have not changed the ContentPropertyAttribute of your derived class DecoratedItem to be something other than Content). Thus, your DataTrigger binding can correctly evaluate:
<DataTrigger Binding="{Binding ElementName=wContent, Path=Content.Visibility}" Value="Collapsed">
Contrast the working case with the failing one:
<c:DecoratedItem Style="{StaticResource DecoratedItem1}" Content="ABC">
in which the Content property is set to an instance of a String, which does not have a Visibility property.
The other way to describe what's wrong is to note that, even though you supply a DataTemplate for the case of Content being a String, Content is still a String and still does not have a Visibility property. In other words, the statement that the Content is generated by the DataTemplate is incorrect -- your DataTemplate just told the control how to display Content of type String.
The general answer to your question is that, if you want the DataTrigger in DecoratedItem1 bound with a Path of Content.Visibility, you need to make sure the content you put in it is always a UIElement. Conversely, if you want to be able to put any sort of Content into the control, you need to trigger off of something else.
The specific answer to your question, strictly, relies on your broader intent (in particular, on how the Visibility of the Content of your control will be set/modified). A couple of possibilities:
if you really want your DataTrigger binding of the form, "Content.Visibility", make sure that the Content is always a UIElement. For instance, use the working form of the style and then bind the Text of TextBlock to something appropriate. However, this doesn't fit so well with the idea of your derived control as a ContentControl, so...
your DataTrigger could probably bind to something else. It seems like, from the way the question is formed, that there is some other property or code-behind that will control whether the various content entities are Visible or not.
finally, you could add an additional DataTrigger to the TextBlock. This DataTrigger would set the visibility of its parent based on its own visibility. Then, bind the DataTrigger in style DecoratedItem1 with Path "Visibility" instead of "Content.Visibility", essentially chaining together Visibilities manually.
Edit
Based on what you've described about how you want to use this, it sounds like you need to consider the visual tree. You might augment your DecoratedItem control to have the following functionality: if all its visual children that are UIElments have a visibility of Collapsed (or if it has no visual children), it is also Collapsed (or, whatever logic makes sense for the desired functionality in terms of the Visibility of its visual children). You'd need to use the VisualTreeHelper class from code -- in particular, the GetChildrenCount and GetChild methods. You'd also, in your DecoratedItem class, override OnVisualChildrenChanged (while still calling the base class method) so that you can get UIElement.IsVisibleChanged events for the visible children.
We are developing usercontrols with .NET 4 - WPF.
We have a lot of customers who are running our application in a remote session (e.g. Terminal-Server, Citriy, etc.).
Many of the performance issues are already solved. At the moment I'm searching for a way to disable animations in styles and controltemplates depending on the condition of IsRemoteSession and/or IsSoftwareRendering. I will do that without writing a line of code.
I'm pretty sure that I read a blog article some months ago in which it described a way to do that using a trigger in xaml, but I can't find it anymore.
Anyone have any hints....?
The way I would do this is by putting the animations inside the triggers. The code would look something like this:
<Window.Resources>
<Style TargetType="{x:Type WhateverYourTypeIs}">
<Style.Triggers>
<!-- Here I assume your whatever holds your IsRemoteSession property is your DataContext -->
<!-- Otherwise, change your binding -->
<DataTrigger Binding="{Binding Path=IsRemoteSession}"
Value="False">
<!-- Here you use Setters to add your animations -->
</DataTrigger>
</Style.Triggers>
</Window.Resources>
I have recently started investigating the MVVM pattern with WPF for an upcoming project. I started with Josh Smith's MSDN article. I have a question (well many, but let's start with one):
I have an IndividualViewModel which exposes the properties of the model. I need two views "Add Individual" and "Edit Individual" which are very similar as you can imagine. What I have done currently is to have 2 subclasses AddIndividualViewModel and EditIndividualViewModel which expose the Add and Edit commands respectively. I also have 2 similary named views that bind to these.
Now this method works and these classes are fairly small anyway, but I'm wondering if it is possible for me to have just the one view model, which exposes both commands. I would still have 2 views which would bind to this same view model, exposing the appropriate command as a button. I'm not quite sure how to do this. In the main window resources I have something like:
<DataTemplate DataType="{x:Type ViewModels:AddIndividualViewModel}">
<Views:AddIndividualView />
</DataTemplate>
With this method of binding you can only have a one-to-one binding, i.e. the same view is always shown for a given view model. Is there a way to automatically switch the view depending on a property on the view model (e.g. IndividualViewModel.Mode). Is there a different approach I should be considering?
Note that the main window has a collection of view models and shows each in tab.
Thank you!
So you need 2 different views based on a property value. One thing to consider is to refactor your presentation code, so instead of the values of a property you could have real subclasses. Then you can use 2 different DataTemplate for each class.
<DataTemplate DataType="{x:Type ViewModels:AddIndividualViewModel}">
<Views:AddIndividualView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:EditIndividualViewModel}">
<Views:EditIndividualView />
</DataTemplate>
If you think that is an overkill, you could use a trigger and wrap your specific views into a ContentPresenter.
<DataTemplate x:Key="AddIndividualTemplate" DataType="{x:Type ViewModels:IndividualViewModel}">
<Views:AddIndividualView />
</DataTemplate>
<DataTemplate x:Key="EditIndividualTemplate" DataType="{x:Type ViewModels:IndividualViewModel}">
<Views:EditIndividualView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:IndividualViewModel}">
<ContentPresenter Content="{Binding}">
<ContentPresenter.Style>
<Style TargetType="ContentPresenter">
<Setter Property="ContentTemplate" Value="{StaticResource AddIndividualTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Mode}" Value="{x:Static ViewModels:IndividualMode.Edit}">
<Setter Property="ContentTemplate" Value="{StaticResource EditIndividualTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
</DataTemplate>
Thanks for pointing me in the right direction! I am still new with WPF too and learning about all the different possibilities including binding methods. Anyway for anyone interested, here is the solution I arrived at for this particular case:
I decided I wanted to keep the view models separated in two subclasses AddIndividualViewModel and EditIndividualViewModel which only expose commands, rather than trying to manage state in the one class. However I wanted one view so that I'm not duplicating the XAML. I ended up using two DataTemplates and DataTemplateSelector to switch out the action buttons depending on the view model:
<DataTemplate x:Key="addTemplate">
<Button Command="{Binding Path=AddCommand}">Add</Button>
</DataTemplate>
<DataTemplate x:Key="editTemplate">
<Button Command="{Binding Path=UpdateCommand}">Update</Button>
</DataTemplate>
<TemplateSelectors:AddEditTemplateSelector
AddTemplate="{StaticResource addTemplate}"
EditTemplate="{StaticResource editTemplate}"
x:Key="addEditTemplateSelector" />
and a content presenter at the bottom of the form:
<ContentPresenter Content="{Binding}"
ContentTemplateSelector="{StaticResource addEditTemplateSelector}" />
Here is the code for the template selector:
class AddEditTemplateSelector : DataTemplateSelector
{
public DataTemplate AddTemplate { get; set; }
public DataTemplate EditTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is AddIndividualViewModel)
{
return AddTemplate;
}
else if (item is EditIndividualViewModel)
{
return EditTemplate;
}
return null;
}
}
This may or may not be how implement the final thing (given the requirements) but it's good to see I have this sort of option available.
There's no reason why you shouldn't be able to achieve that. One way of doing this is to provide some flag in your view model stating whether you're in add mode or in edit mode, and styling your view based on that flag using simple bindings, triggers or template selectors.
For reference you may look at Sacha Barber's DataWrapper class that's part of his Cinch framework (not directly applicable to your case, but it's a good starting point) which wraps data fields in the view model in such a way to support a flag to toggle between read only (view record mode), and read-write (edit record mode). You could apply a similar approach to make the distinction between add and edit.
Basically, instead of having simple properties in your view model, instantiate a data wrapper class which includes a Value property, and a IsAdding property. In your view, you can use bindings, triggers or template selectors to modify templates based on that property.
For this task you do not need any DataTemplateSelector at all.
Derive both EditIndividualVM and AddINdividualVM from IndividualVM.
The Edit- and AddCommands route to a setter property in the IndividualVM.
The setter VM = new AddIndividualVM or VM = new EditIndividualVM depending on which button is pressed.
In xaml you bind in the contentgrid to your VM property like this: