Put different kinds of user-controls in a wpf grid - c#

I'm starting with WPF, and I want to create a grid which shows shapes according to the context. So for example in one cell, I can have either a circle, or a rectangle !
I created my custom circle "Circle.Xaml" and my custom rectangle "Rectangle.Xaml". I created also their View-Models "CircleVM.Cs" and "RectangleVM.Cs".
Now, I added a listBox to my application to let the user to put either the circle, or the rectangle in my grid cell.
My problem is:
How can I bind all that with my cell ? In order to put the right control when I'll add simply his corresponding View-Model in the collection binded to my grid !!
How can I set the DataContext of a view when the view-Model constructor has arguments which are not defined at the initialisation (Values of arguments are according to the context too) !!

When you declare a DataTemplate in a Resources section, you have a choice... you can provide it with a name and then use that name to reference it, or you leave it without a name. If a DataTemplate has no name (or x:Key value) then it will automatically be applied to all objects of the type specified in it unless they have another DataTemplate explicity set with a named DataTemplate.
Therefore, you can add the following into your application Resources section (in App.xaml) and it will apply to all of the view models in your application:
<DataTemplate DataType="{x:Type ViewModels:CircleVM}">
<Views:Circle />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:RectangleVM}">
<Views:Rectangle />
</DataTemplate>
Please note that you will need to add the required XML Namespaces... something like this:
xmlns:ViewModels="clr-namespace:YourApplicationName.FolderNameIfApplicable"
xmlns:Views="clr-namespace:YourApplicationName.FolderNameIfApplicable"
Now, whenever you add an instance of your CircleVM class into the UI, the related Circle.xaml file will be shown:
<ContentControl Content="{Binding YourViewModelProperty}" />
To switch between the two view models, create a property as above... again, you have two choices:
public object YourViewModelProperty // with valid getter/setter removed here
Or better, create a BaseViewModel class that implements the INotifyPropertyChangedinterface and extend all of your view models from it:
public BaseViewModel YourViewModelProperty // with valid getter/setter removed here
The final step to take when the user changes their selection is to set the value of this property:
if (userHasChosenCircle) YourViewModelProperty = new CircleVM();
else YourViewModelProperty = new RectangleVM();

Related

"Dynamically" Generate a WPF View C# for Plotting with DynamicDataDisplay

I am writing a WPF application in C#. This application is design using MVVM.
Currently, I have a parent window with a few check boxes. Use user can check whichever boxes they want and then click the "plot" Button. Once they click "plot", a new child window comes up displaying the data on a single graph.
So, if I have only 1 check box checked, and then click "plot", I will see a graph with a single line on it. If I have 2 check boxes check and click "plot", I will see the same single graph, but it will have 2 lines on it.
My current Implementation:
Currently, I have a "view" class called GraphWindowView. The view obviously needs to know of which data to show. So to do that, I have dependency properties GraphWindowView.Dates and GraphWindowView.Data which ultimatley produces a graph of Data (y axis) vs. Dates (x axis).
Question: This current implementation of GraphWindowView is obviously restricted to only being able to graph one set of data (i.e. Data vs. Dates). I would like to make this (a lot) more extensible and have an arbitrary number of plots available depending on how much check boxes are checked. How would I go about doing this? I think I need to rethink my use of dependency properties...
>>> UPDATE
So I made a GraphLine class which should represent a line on the graph. The "graph" is actually a ChartPlotter element in the GraphWindowPresenter.xaml class. Additionally, I specified a DataType for the GraphLine objects, but that is all I understand. What are the next steps to this, how do I actually add the data to the graph? And how/where do I make instances of GraphLine to populate the ChartPlotter element? Sorry I am pretty lost on this, even after reading quite a few tutorials. Thanks for all the help so far, I really appreciate it!
GraphWindowView.xaml
<Window x:Class="BMSVM_Simulator.View.GraphWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModel="clr-namespace:BMSVM_Simulator.ViewModel"
xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0"
x:Name="ThisGraphWindowInstance"
Title="Plot" Height="500" Width="750"
Icon="../res/qualcomm_q_icon.ico.ico"
MinWidth="400" MinHeight="300">
<Window.DataContext>
<ViewModel:GraphWindowPresenter/>
</Window.DataContext>
<d3:ChartPlotter Name="plotter" Margin="10,10,20,10">
<d3:ChartPlotter.HorizontalAxis>
<d3:HorizontalIntegerAxis Name="dateAxis"/>
</d3:ChartPlotter.HorizontalAxis>
<d3:ChartPlotter.VerticalAxis>
<d3:VerticalIntegerAxis Name="countAxis"/>
</d3:ChartPlotter.VerticalAxis>
<d3:Header FontFamily="Arial" Content="{Binding ElementName=ThisGraphWindowInstance, Path=title}"/>
<d3:VerticalAxisTitle FontFamily="Arial" Content="{Binding ElementName=ThisGraphWindowInstance, Path=yAxis}"/>
<d3:HorizontalAxisTitle FontFamily="Arial" Content="{Binding ElementName=ThisGraphWindowInstance, Path=xAxis}"/>
</d3:ChartPlotter>
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModel:GraphLine}">
<!--WHAT GOES HERE-->
</DataTemplate>
</Window.Resources>
</Window>
GraphLine.cs
namespace BMSVM_Simulator.ViewModel
{
class GraphLine
{
public string xAxis { get; private set; }
public string yAxis { get; private set; }
public string title { get; private set; }
public string legend { get; private set; }
public EnumerableDataSource<int> data { get; private set; }
public EnumerableDataSource<int> dates { get; private set; }
}
}
Most of these types of problems in WPF can be sorted out by some careful use of data binding and DataTemplates, rather than miles of procedural code. The general idea is that you create a custom class with all of the properties that are required to draw all of your lines. You would then declare a DataTemplate to define how the various properties are to be data bound, perhaps a little something like this:
<DataTemplate DataType="{x:Type YourXamlNamespacePrefix:GraphLine}">
<Line X1="{Binding X1}" Y1="{Binding Y1}" X2="{Binding X2}" Y2="{Binding Y2}" />
</DataTemplate>
Then you create a collection of your custom class instances and data bind it to some collection control, like an ItemsControl and each one will be automatically rendered in the correct location:
<ItemsControl ItemsSource="{Binding YourGraphLineCollection, RelativeSource={
RelativeSource AncestorType={x:Type YourXamlNamespacePrefix:YourControlName}}}" />
Welcome to the powerful world of WPF data binding and DataTemplates.
UPDATE >>>
The custom class to data bind to the Line elements is not a view model. Think of it as a data type class, for which you will declare a DataTemplate like the one above. When I said that it should have all of the required properties, if you look at the above example, you'll see that it would at least need four double properties to data bind to the four used properties of the Line element. However, you might also choose to add further properties to data bind to the Stroke, StrokeThickness or Fill properties for example.
As for where you should define the DataTemplate, it should be within scope of the items that have it applied. If you want to use it in one view, then put it in the UserControl.Resources section of that view. However, if you want to use the same DataTemplate, then you should put it into the Application.Resources section of the App.xaml file because those Resources are available application wide.
FINAL UPDATE >>>
As noted in my comment, teaching users how to use WPF is definitely out of scope for this website, so I won't be doing that. To learn about DataTemplates, you should read the Data Templating Overview page on MSDN. When you don't know about something, MSDN should always be your first place to search for answers.
I can give you a few last tips before I go: The DependencyProperty in your control should be of type ObservableCollection<GraphLine>. Inside your control, you should data bind them to some sort of ItemsControl as shown above - I changed the Binding Path in it because you should really use a RelativeSource Binding to locate the property in your situation (where YourControlName is the name of your UserControl where you want to draw the Line objects).
Finally, in your view model (that is linked with the view that contains your new UserControl that draws the lines), you'll need a collection property to data bind with the collection in the UserControl, let's say named YourGraphLineCollectionInViewModel:
<YourXamlNamespacePrefix:YourControlName YourGraphLineCollection="{Binding
YourGraphLineCollectionInViewModel}" />
It's in this view model that you add the instances of your GraphLine class into the YourGraphLineCollectionInViewModel collection and as long as you have set up your Binding Paths as shown here, they'll appear in your UI within the ItemsControl. I am assuming that you know how to correctly set your DataContext - if not, you can easily find out how to do that online.

Josh Smith MVVM Demo app

In MainWindow we have:
<HeaderedContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{StaticResource WorkspacesTemplate}"
Header="Workspaces"
Style="{StaticResource MainHCCStyle}"
/>
In the resources:
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
</DataTemplate>
And in the article says:
A typed DataTemplate does not have an x:Key value assigned to it, but
it does have its DataType property set to an instance of the Type
class. If WPF tries to render one of your ViewModel objects, it will
check to see if the resource system has a typed DataTemplate in scope
whose DataType is the same as (or a base class of) the type of your
ViewModel object. If it finds one, it uses that template to render the
ViewModel object referenced by the tab item's Content property.
My question is:
How does the template know that the type is a collection of workspaces (WorkspaceViewModel)?
It doesn't need to, in the code you've posted. In your sample, you have given a strict value to your content template: you've explicitly used {StaticResource WorkspacesTemplate}, and so a resource with the key of "WorkspacesTemplate is looked up.
Because you've explicitly set the template, it doesn't matter what the intended type is: it'll try to display any object in your Content using the template you've set - with varying degrees of success if you use a type that doesn't match!
In the alternate method you mention - with a "typed DataTemplate", you would declare your datatemplate with <DataTemplate DataType="{x:Type l:WorkSpace}" />. Note that there is no x:Key (and also that I've assumed you have a namespace l mapped to your local code). What happens here is that WPF automatically sets the key of your resource to the DataType (important to note: a resource key doesn't have to be a string!).
Then, when you declare your HeaderedContentControl, you can leave out setting the ContentTemplate. At runtime, when the control is rendered, WPF will check the type of the Content object and find that it is WorkSpace, and it'll then look up a resource with x:Key="{x:Type l:WorkSpace}" - which will match your typed template.
This is a useful way of making consistent representations of data throughout your application, since a typed DataTemplate will be used automatically by any content-presenting control throughout your application.
WPF doesn't really care about the concrete type, it's just need to be some IEnumerable of something, WPF uses the type descriptor to know what the ui binding with.

ViewModels that talk to each other without a Framework

Introduction
I have an application that imports lab instrument data while it is running. This data is imported and then displayed in a ListView at an interval set by the end-user as per his or her testing requirements. When a value of interest appears in this ListView that they watch, they then press a Start button and the application begins performing calculations on that datum and subsequent data until a Stop button is pressed. So on the left side of the screen is a View for displaying the imported data and on the right side is another View for watching the values and statistics as they are calculated and displayed.
The Current Code
The View that displays the ListView where data is imported to is the ImportProcessView.xaml and it sets its DataContext to the ImportProcessViewModel.cs. The VM I've just introduced has a property ObservableCollection<IrData> that the ListView, I've also just described, binds to. Now to the interesting part...
The ImportProcessView has a ContentControl that sets it's content dynamically a UserControl representing the controls and fields specific to the type of Phase that is chosen by the end-user.
<StackPanel Background="White" Margin="5">
<ContentControl Content="{Binding CurrentPhaseView}"/>
</StackPanel>
There are three PhaseViews, each in its own User Control and each sets it's DataContext to the ImportProcessViewModel. As a result I am getting some severe VM bloat to the tune of 2000 lines. Ridiculous. I know. The reason for the bloat is because the ImporProcessViewModel is maintaining state through properties for each of the three PhaseViews and not only that but contains methods for performing calculations whose data is stored and displayed in these "PhaseViews".
What I am trying to achieve
Obviously before the ImportProcessViewModel becomes more unwieldy, I need to break it up so that each PhaseView has its own ViewModel, but also such that each ViewModel maintains a relationship back to the ImportProcessViewModel for sake of the dependency imposed by the ObservableCollection of IrData.
R&D
I've done my research on ViewModels communicating with each other, but most of the results involve applications that were written with a specific MVVM framework. I am not using a framework, and at this point in the project it would be too late to refactor it to start using one.
I did, however, find this article and the answer offered by 'hbarck' suggests something simple like composition to achieve the result I want, but since I don't have much experience with DataTemplates I don't understand what is meant when he/she suggests exposing "the UserControl's ViewModel as a property on the main ViewModel, and bind a ContentControl to this property, which would then instantiate the View (i.e. the UserControl) through a DataTemplate"
Specifically, I don't understand what is meant by "bind a ContentControl to this property which would then instantiate the View through a DataTemplate".
Can someone clarify by way of an code example what is meant by instantiating a view through a DataTemplate in the context of this example?
Additionally, is this a good approach (as suggested by 'hbarck')?
As one can see, I am already setting the Content property of a ContentControl to the Phase View that is to be instantiated. I just don't know know what involving a DataTemplate would look like.
I don't understand what is meant when he/she suggests exposing "the
UserControl's ViewModel as a property on the main ViewModel, and bind
a ContentControl to this property, which would then instantiate the
View (i.e. the UserControl) through a DataTemplate"
A DataTemplate allows you to specify a relationship between a view (such as a user control) and a view model.
<DataTemplate DataType="{x:Type myApp:MyViewModel}">
<myApp:MyUserControl />
</DataTemplate>
This tells a ContentPresenter to display MyUserControl whenever its content property is set to an instance of MyViewModel. The view model will be used as the user controls DataContext. Typically, the DataTemplate is added to your application resources.
What the author of that answer is saying is that you could have a viewModel that has a property of another viewModel type which is bound to the Content property of the ContentPresenter.
<ContentPresenter Content="{Binding ParentViewModel.ChildViewModelProperty}"/>
Providing you have a DataTemplate that specifies a relationship between your ChildViewModel and your user control, WPF will automatically load the user control into your view.
This answer I provided to another question might also provide you with some help.
I need to break it up so that each PhaseView has its own ViewModel,
but also such that each ViewModel maintains a relationship back to the
ImportProcessViewModel.
This will allow you to break your viewModels into smaller, more manageable viewModels that look after themselves. This will leave you with the problem of communicating between the viewModels.
If you nest your viewModels as suggested, then your child viewModels could expose events that the parent viewModel can bind to so it is notified when something changes. Something like this:
public class ParentViewModel // Derive from some viewModel base that implements INPC
{
public ParentViewModel()
{
childViewModel = new ChildViewModel();
childViewModel.SomeEvent += someEventHandler;
// Don't forget to un-subscribe from the event at some point...
}
private void SomeEventHandler(object sender, MyArgs args)
{
// Update your calculations from here...
}
}
This is simple and doesn't require any additional frameworks. Some might argue against this method but it is a valid solution that works. The downside is that the viewModels have to know about each others existence in order to subscribe to the events so can end up being tightly-coupled. You can use standard object-oriented design principles to get around this though (I.E. derive your child viewModel from an interface so that the parent only knows about the interface and not the implementation).
If you really want to go for loosely-coupled communication then you need to use some sort of event aggregation or message bus system. This is similar to the above method except there is an object that sits between the view models and acts as a mediator so that the viewModels do not have to know of each others existence. My answer here provides some more information.
There are pre-existing solutions available but this would involve taking on an additional framework. I would advise using Josh Smiths MVVM foundation as it is very simple and you would only need to use a single class anyway.
While Benjamin's answer is really elaborate and very helpful, I'd like to clarify how what I wrote in the other post would apply to your problem:
You'd have three different PhaseViewModel-Classes for your different phases, probably derived from one common base class, let's say PhaseVMBase.
Instead of a CurrentPhaseView property, you'd probably have a CurrentPhaseVM property. This would be of type Object or PhaseVMBase, and return one of the three PhaseViewModel classes, depending on what the user chose in the main ViewModel.
PhaseVMBase would have an UpdateData method, which would be called by the main ViewModel whenever it received new data that should be processed by the phase view. The main ViewModel would call this method on whatever happened to be the CurrentPhaseVM at the moment. The PhaseViewModels would implement INotifyPropertyChanged, so that changes as a result of UpdateData would be visible to bound controls.
Your DataTemplates would be declared in the resources of the main view, e.g. the main window,
like this:
<DataTemplate DataType="{x:Type my:Phase1VM}">
<my:Phase1View/>
</DataTemplate>
<DataTemplate DataType="{x:Type my:Phase2VM}">
<my:Phase2View/>
</DataTemplate>
<DataTemplate DataType="{x:Type my:Phase3VM}">
<my:Phase3View/>
</DataTemplate>
Notice that there is no x:Key, only the DataType value. If declared like this, WPF would choose the appropriate DataTemplate when asked to display an object of type Phase1VM, Phase2VM or Phase3VM, respectively. Phase1View, Phase2View and Phase3View would be UserControls which would know how to display the different ViewModels. They wouldn't instantiate their ViewModels themselves, but expect that their DataContext is set to an instance of their respective ViewModel from outside.
Under the assumption that the ContentControl which should show the phase view is declared in the main view, and that the DataContext there would be the main ViewModel, you'd declare the ContentControl like this:
<ContentControl Content="{Binding CurrentPhaseVM}"/>
Depending on the actual type of CurrentPhaseVM, this will choose one of the three DataTemplates, and display the appropriate UserControl. The UserControl's DataContext would automatically be the ContentControl's Content, since that would the object which caused the DataTemplate to be chosen.
EDIT: Lists and code formatting don't go together, it seems...

Data Template are only necessary in ItemsControl controls?

Let me explain you my situation.
I have a base class called Shape, and several concrete classes like Triangle, Square, etc.
I have several data templates.
I'm building just one object. So I wouldn't use an ItemControl control, I would like to use a normal panel like the grid, and show the respective data template (in DataContext has the concrete item)..
The only way to do this is using an ItemsControl? Or there's another way.. because I'm just using one item and not a collection and display the correct template.
DataTemplates are used in much more than just ItemsControls
They are used to tell WPF how to draw any object in the Visual Tree. For example, if you stick a User class object in the VisualTree, a DataTemplate can be used to tell WPF how to draw that User object
They are most frequently used in controls with an ItemsSource or Content properties, because those are the most common way of inserting data objects into the VisualTree.
In your specific case where you only want to insert one data item into the VisualTree, I would suggest a ContentControl
<ContentControl Content="{Binding MyDataObject}" />
To tell WPF how to draw MyDataObject you can either use the ContentTemplate property and set it to a DataTemplate
<ContentControl Content="{Binding MyDataObject}"
ContentTemplate="{StaticResource MyDataTemplate}" />
or define an implicit DataTemplate that tells WPF to draw any object of a specific type using a specific template.
<DataTemplate DataType="{x:Type local:MyDataObject}">
<!-- Tell WPF how to draw MyDataObject here -->
</DataTemplate>
If you want to display a single item with a data template that is selected based on the item's type, you should use ContentControl or any of its derived classes.

how to create binding in code when the datatemplate is declared in xaml

i have a datatemplate declared in xaml.
for e.g.
<DataTemplate x:Key="TestTemplate">
<StackPanel>
<TextBox Name="txtBox" Visibility="Visible"></TextBox>
</StackPanel>
</DataTemplate>
I wish to set the binding for txtBox in code behind before the element is generated because i have different binding paths for different elements that get generated
I can get the template in the code behind as :
DataTemplate tmplt = FindResource("TestTemplate") as DataTemplate;
but i am not sure what to do next. How to get the the txtBox reference to set the binding.
We have to remember one thing that Templates are not instantiated UI controls. They are streamed obejcts in XAML and are shared between UI elements. So if you edit a dataTemplate and change its stucture (by adding, editing, deleting an element under the template) it would change the one data template which is shared among controls. Thus other elements using that template will also be affected by the change.
Now lets address your issue of adding a dynamic biding to a textbox. You say each generated textbox will have different binding paths. So this definitely does NOT call for changing the data template itself!
You will have to access the text box and add dynamic bindings to it AFTER the textbox's is generated.
I see that your binding differs based on your "situation", so why cant you use TemplateSelector? Template selector will decide which data template (having one specific binding applied to the TetxBox) at runtime.
The first part of answer - is FindName() method.
example:
DataTemplate tmplt = FindResource("TestTemplate") as DataTemplate;
TextBox my = (TextBox)tmplt.FindName("txtBox");
try out this, it should help to get access to TextBox control. I think that you know how to bind to. If you want your DataBinding behave different way, use MultiBinding and Converter.
EDIT
public class GeneralObject
{
private object someObject;
public GeneralObject(object initObject)
{
this.someObject = initObject;
}
//If you want to bind to some text, for example
public string Text
{
get
{
//I think you know which objects are coming as input
if (this.someObject is SpecialClass1)
return ((SpecialClass1)this.someObject).SpecialClass1TextProperty;
if (this.someObject is SpecialClass2)
return ((SpecialClass2)this.someObject).SpecialClass2TextProperty;
//and so on.
}
}
}
EDIT 2
One more possible way
So I remember, that WPF have ContentControl!
<ContentControl Content="{Binding Path=CurrentObject}"/>
But in this case you have to create number of DataTemplate's, every Template for one class.
<DataTemplate DataType="{x:Type local:SpecialClass1}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type local:SpecialClass2}">
...
</DataTemplate>
<!--and so on-->
WPF resolve DataTypes of ContentControl.Content property, and put to the ContentControl right DataTemplate.

Categories

Resources