This question is more about neat programming along the MVVM lines rather than a 'how-do-I-get-this-to-work' one.
I wanted to keep all the ControlTemplates for my DataTemplates in a separate ResourceDictionary file, for clarity. So, in the main Window it looks like this, e.g:
<DataTemplate DataType="{x:Type localm:MusicSystem}">
<Control Template="{StaticResource MusicSystemTemplate}"/>
</DataTemplate>
However, the ControlTemplate for this very Class contains a ListBox which is supposed to react to certain events - have some handlers, as here:
<ControlTemplate x:Key="MusicSystemTemplate">
<StackPanel ... >
<ListBox x:Name="SystemListBox" ...
PreviewMouseMove="SystemListBox_PreviewMouseMove"
PreviewMouseDown="SystemListBox_PreviewMouseDown"
MouseEnter="SystemListBox_MouseEnter"
MouseLeave="SystemListBox_MouseLeave">
<!-- More ListBox stuff here -->
</ListBox>
<!-- More other stuff here -->
</StackPanel>
</ControlTemplate>
For now I'm keeping this template in the XAML of the main Window, and the handlers in its code behind, but that's not what I want. If it was a Button, I'd create a command and bind it one way or the other. But since it's a ListBox, I'm unfortunately deprived of that possibility. I wouldn't like to resort to the code behind of the ResourceDictionary, because this is not what they are for. Is there a neat and not overly verbose way around it? Or maybe there is something wrong with declaring handlers within a Template altogether?
You can use attached behaviors for this. They can either be behaviors attached using Blend's Interactivity assembly, or just attached properties which add and remove event handlers when their values are changed.
Overview: Behaviors in WPF introduction.
Related
I'm about to create a dynamic WPF UI form from DataTable data. The screens would be fairly complex. They would contain textboxes, groupboxes, checkboxes, buttons, datagrids etc. Some of them visible, some hooked up event handlers and thing like that.
What approach of creating those dynamic screens would you choose considering performance impact and complexity requirements to write and maintain source code. Please note that this code will run a LOT so it must be efficient and blazing fast. I'm considering these options:
Create Controls in code, assemble them to a tree and use the tree (Grid control) as a root element for a WPF form.
1.a) Create a XAML via XAMLReader from that screen object tree and Load it via XAMLReader inside WPF Form. Creating XAML would seem redundant to me since I can use the built tree as a Content for WPF form directly.
Use XMLDocument class to create tags, obejcts and their atributes. Create a XAMLlike that and then load that XAML in WPF form.
Thanks,
Michal
Consider displaying your form in a listview and creating a DataTemplate for each of your form fields textboxes, groupboxes, checkboxes, buttons, datagrids etc.
<ListBox ItemsSource="{Binding DataFormFields}"
<DataTemplate DataType="YourTextClass">
<StackPanel>
<TextBlock Text="{Binding LabelText}" />
<TextBox Text="{Binding ValueText}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="YourCheckClass">
<StackPanel>
<CheckBox Content="{Binding LabelText}"
IsChecked="{Binding Checked}"/>
</StackPanel>
</DataTemplate>
For more on DataTemplates see
https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/data-templating-overview
Each data template should be associated with a one of your form fields classes, using the DataType attribute, this will cause the listbox to automatically use the correct DataTemplate.
For more details:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.datatemplate.datatype?view=netframework-4.7.2
I am pretty new to WPF, and in order to get some knowledge I decided to make a very simple UML modeling program, that basically offers the possibility to put some classes onto a canvas, connect them and move them around.
Now to the question:
I have been thinking about letting the classes I put on the canvas being a userControl I design. In my mind it would be something like a Grid, with some textboxes to represent properties, attributes and so on. The actual question is then, is my idea possible, or should I go with something completely different? My concern right now is how to implement the grid such that it can expand (add a row) under the right heading (Attribute/property..) when I want it to, and not be expanded to a maximum from the beginning.
I hope you can understand my question, and give me an idea to whether I should continue to implement it how I thought about, or do it using some other method.
You may wish to consider a ListView control, perhaps with an Expander, something like this:
<Canvas>
<Expander Header="Stuff"
MaxHeight="900"
Canvas.Left="202"
Canvas.Top="110">
<ListView Name="MyListView">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Add new thing"
Click="MenuItem_Click" />
</ContextMenu>
</ListView.ContextMenu>
<ListViewItem>
<StackPanel Orientation="Horizontal">
<Label>Name</Label>
<TextBox Text="Value" />
</StackPanel>
</ListViewItem>
<ListViewItem>Item two</ListViewItem>
<ListViewItem>Item three</ListViewItem>
</ListView>
</Expander>
</Canvas>
This will size as needed up to the max given. The list view items could contain any sort of content (not just text) as you can see above. You will want to learn a bit about Style and Control templates. WPF has IMHO a rather steep learning curve but there are a lot of learning resources on the web. Good luck.
In response to your comment, I'm adding additional information.
Anything you can do in XAML you can do in code behind (mostly XAML just calls framework objects). In this case I've added a context menu to the ListView control. This menu contains one item "Add new thing". There is a Click event for this item which is bound to the MenuItem_Click method in the code behind. I then added this method to the code:
void MenuItem_Click(object sender, RoutedEventArgs e) {
var lvi = new ListViewItem();
lvi.Content = String.Format("New thing {0}", DateTime.Now);
MyListView.Items.Add(lvi);
}
Now if you right click in the ListView you will see the "Add new thing" menu selection, left clicking it adds a new ListViewItem into the ListView (programmatically).
I have a listbox in WPF that will contain a list of ResultsViewModel items, however the actual runtime type of these objects could be
CalculateResultsViewModel,
ScenarioResultsViewModel,
GraphResultsviewModel etc etc,
all of which extend the base abstract class ResultsViewModel.
Each of these view models should be rendered differently in the ListBox so needs a different DataTemplate. I can do that just with XAML easy enough. The difficulty is that when the viewmodels are either "processing" or when they have failed", I need them to display a DataTemplate for "processing" or "errored" which I can only so far do with Triggers. That however then means I can't use the DataTemplateSelector or a basic XAML style.
The only solution I can think of (not clean I know) is to set the DataTemplate programmatically in the SetResult() method of each viewmodel class, which is what gets called when the processing completes either successfully or with an error. In that DependencyProperty I can look at the return code and then programatically set the DataTemplate depending on the sucess/failure result. The only problem is I cannot figure out how to
Obtain a DataTemplate resource from a ResourceDictionary just using c# code - bearing in mind Im calling all of this from the viewmodel class, not the window code-behind .xaml.cs file so it doesn't have access to the properties of Window
having only a handle to the viewmodel class, somehow obtain a reference to the ListBoxItem that contains it and then programmatically set the DataTemplate on this container.
Can anyone point me in the right direction?
you can take the magic with implicit datatemplates
<ListBox ItemSource={Binding YourResults}>
<ListBox.Resources>
<DataTemplate DataType={x:Type CalculateResultsViewModel}>
<Grid></Grid>
</DataTemplate>
<DataTemplate DataType={x:Type ScenarioResultsViewModel}>
<Grid></Grid>
</DataTemplate>
<DataTemplate DataType={x:Type GraphResultsviewModel }>
<Grid></Grid>
</DataTemplate>
</ListBox.Resources>
</ListBox>
for "processing" or "errored" viewmodels you can specify a adorner overlay in all yout datatemplates (ok but you must use the triggers)
hope this helps
On a project I have started using Caliburn.Micro.
Now I want to split a large UserControl into several smaller UserControls, so that I can use them inside of DataTemplates. EisenbergEffect suggests to do so in this answer
The ViewModels for those are already there, as I modelled the hierachical data as ObservableCollection<SubViewModel>.
Now I thought I just have to create the suiting Views as UserControls for those SubViewModels.
My view is a Master-Details view. I want to present a list of Computers and in the Details View I want to have the HardwareComponents of those.
<ListView x:Name="ComputerViewModels">
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl x:Name="HardwareComponentViewModel" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have included Debug output as suggested here: Debug Logging
And I do not get any output about my HardwareComponentViewModel except
Action Convention Not Applied: No actionable element for set_HardwareComponentViewModel
The HardwareComponentViewModels already get created by the time the ComputerViewModels get created.
I already use the ContentControl - way on other parts of the application and it works very well there (getting the Content using IOC injected into the outer ViewModel).
But it does not seem to fit for the DataTemplate part (which is not very surprising, though). The SubViewModels are derived from Screen, as the outer ViewModels.
How can I use the new UserControl as DataTemplate?
Take a close look at EisenbergEffect's answer. It's explicitly stated, that conventions don't work inside DataTemplate, so if you have a complicated template, it's better to move it out to a separate UserControl, inside which conventions will work again.
That means you have to explicitly bind the model inside the template:
<ListView x:Name="ComputerViewModels">
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl cal:View.Model="{Binding HardwareComponentViewModel}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
That should do it. Either that or cal:Model.Bind="{...}", I can never remember the difference and use case. Now inside the bound UserControl (HardwareComponentView, I presume) the conventions between view and the view model should work as usual.
I'm wondering how to go about creating different views in the main window when a button is pressed. I'm not sure of the correct terminology, so that has hampered my google fu.
I'm thinking that the main viewing area would be a content control, that I could change when a event happens. I made a small drawing to help illustrate my idea / thought.
Any input will be appreciated. Thanks!
It would be really easy to implement this senario using MVVM approach....
Make a ViewModel for you MainView. Then Define Properties of the ViewModels of your UserControls
For Example You have Two UserControl as FirstView and SecondView then make a properties in your viewmodels as ViewToLoadProperty of the type ViewModel (usually called as ViewModelBase)
Set bindings as
<!-- Panel For Hosting UserControls -->
<Border Grid.Column="2">
<ContentControl Name="userControlContentControl"
Content="{Binding Path=ViewToLoadProperty,
}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type ViewModelLayer:FirstViewModel}">
<ViewLayer:FirstView/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModelLayer:SecondViewModel}">
<ViewLayer:SecondView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Border>
<!-- Panel For Hosting UserControls -->
Then when you click the button Use a command to set the respective ViewModel Intance to this(ViewToLoadProperty) property...(Use RelayCommannds or something like it)
DataTempates would do the rest of the job by selecting the right View according to the right type of ViewModel
YOu can use MVVMLight toolkit if you are implementing MVVM Pattern.. :)
On the right you could have a frame. Then the button would bind a different page or user control to the content of that frame.