Is it possible to add and bind user controls dynamically? Maybe I'll show sample code to show what I exactly mean.
MainWindow:
<UniformGrid
Rows="11"
Columns="11"
DataContext="{StaticResource vm}">
<local:DynamicUserControl
ButClickControl="{Binding Path=UserControlObjects[0].ButClickCommand}"
SomeDataInUserControl="{Binding Path=UserControlObjects[0].SomeData, Mode=OneWay}" />
<local:DynamicUserControl
ButClickControl="{Binding Path=UserControlObjects[1].ButClickCommand}"
SomeDataInUserControl="{Binding Path=UserControlObjects[1].SomeData, Mode=OneWay}" />
<local:DynamicUserControl
ButClickControl="{Binding Path=UserControlObjects[2].ButClickCommand}"
SomeDataInUserControl="{Binding Path=UserControlObjects[2].SomeData, Mode=OneWay}" />
.....
</UniformGrid>
In ViewModel there is an array of UserControlObjects. But in this array I will have over 100 elements, so it is not the best option to write all elements one by one. Is there any way to add DynamicUserControls not in XAML but somewhere in code in loop with keeping the MVVM pattern and binding?
Use an ItemsControl with the UniformGrid as ItemsPanel and the DynamicUserControl in the ItemTemplate:
<ItemsControl DataContext="{StaticResource vm}"
ItemsSource="{Binding UserControlObjects}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="11" Columns="11"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:DynamicUserControl
ButClickControl="{Binding ButClickCommand}"
SomeDataInUserControl="{Binding SomeData}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In my opinion, you would want to keep any controls out of your view model. You could however keep the individual view models that back the controls in a list within the main view model. For example, create the view model that will provide the data for the “dynamic” controls.
class SubViewModel
{
public string Name { get; private set; } = string.Empty;
public SubViewModel(string aName)
{
Name = aName;
}
}
And in the main view model you can do whatever you would do to dynamically create instances. In this case, I am just creating then in a for loop.
class MainWindowViewModel
{
public ObservableCollection<SubViewModel> SubViewModels
{
get
{
return mSubViewModels;
}
} private ObservableCollection<SubViewModel> mSubViewModels = new ObservableCollection<SubViewModel>();
public MainWindowViewModel()
{
for(int i = 0; i < 30; i++)
{
SubViewModels.Add(new SubViewModel($"Control: {i}"));
}
}
}
Then in the view, you can utilize an ItemsControl with an UniformGrid based ItemsPanelTemplate, and then whatever you want for the data template, whether you define it there explicitly, or make a user control (like your local:DynamicUserControl) to clean things up. In this sample, the data template it explicitly defined.
<Window x:Class="ListOfViewsSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ListOfViewsSample"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding SubViewModels}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Background="LightGray" Margin="10">
<Label Content="{Binding Name}" Margin="5" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
which results in the following:
If don’t want the multiple dynamic views to be the same, you can look into data template selectors to display something different based on the specified view model, but based in your question I think you were looking for a list of the same control/data. Hope this helps!
The usual way of doing this is:
Create an ItemsControl for the dynamic items you want to create
Override the ItemsPanel to whatever you need (UniformGrid in your case)
Bind it to a list of view models, with one view model per control
Define DataTemplates to map each view model type to its corresponding view type
Related
When I define a DataTemplate inline, Visual Studio knows about the type I'm binding to, and properties in that type come up in autocomplete (for example in the code below I was able to select DisplayName from the autocomplete list inside the FirstViewModel template).
<DataTemplate DataType="{x:Type viewmodels:FirstViewModel}">
<StackPanel >
<Label Content="{Binding DisplayName}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:SecondViewModel}">
<views:SecondView/>
</DataTemplate>
However, when the data template references an external control, as for SecondViewModel in the code above, when I'm in the file for the SecondView usercontrol, since it's just a control, the type isn't bound and the editor doesn't help me with anything.
I've tried wrapping my whole control (inside the UserControl element) in the same DataTemplate tag, but then my whole view just shows "System.Windows.DataTemplate".
<UserControl x:Class="Gui.Views.Tabs.ExamsTabViews.ExamInfoView"
xmlns:vm="clr-namespace:Gui.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<DataTemplate DataType="vm:ExamInfoViewModel">
<DockPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<!-- contents of the template -->
</DockPanel>
</DataTemplate>
</UserControl>
Is there a way to achieve this kind of binding for the editor?
<DataTemplate DataType="{x:Type viewmodels:SecondViewModel}">
<views:SecondView/>
</DataTemplate>
when this DataTemplate is instantiated, there will be created SecondView and that SecondView will have a SecondViewModel in DataContext. So there is no need any DataTemplate in SecondViewModel control - bind to DataContext instead ({Binding SecondViewModelProperty}). To have design-time support for such binding use d:DataContext="{d:DesignInstance}:
<UserControl d:DataContext="{d:DesignInstance Type=vm:ExamInfoViewModel,
IsDesignTimeCreatable=True}" ...>
In my project, I wanted a UserControl that can display different formats of an image (bitmap, svg), selected from a ListBox. The SelectedItem of the ListBox is bound to the appropriate view model, which in turn changes the DataContext of the UserControl, and what I want to achieve is for it to change the displaying control (an Image for bitmaps, a SharpVectors.SvgViewBox for svg files) through data templates. It does so, but raises data binding errors, as if the templates were still intact whilst the UserControl's DataContext has already been changed.
I should like to a) avoid any data binding errors even if they cause no visible problems b) understand what is happening, so I prepared a MWE, which, to my surprise, displays the same behaviour, so I can present it here.
My minimal UserControl is as follows:
<UserControl x:Class="BindingDataTemplateMWE.VersatileControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate DataType="{x:Type system:String}">
<TextBlock Text="{Binding Content.Length, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContentControl}}" />
</DataTemplate>
<DataTemplate DataType="{x:Type system:DateTime}">
<TextBlock Text="{Binding Content.DayOfWeek, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContentControl}}" />
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentControl
Content="{Binding .}" />
</Grid>
</UserControl>
The MainWindow that references this UserControl has the following XAML:
<Window x:Class="BindingDataTemplateMWE.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BindingDataTemplateMWE"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox
Grid.Column="0"
SelectedItem="{Binding Selected}"
ItemsSource="{Binding Items}" />
<local:VersatileControl
Grid.Column="1"
DataContext="{Binding Selected}" />
</Grid>
</Window>
with the following code-behind (to make the MWE indeed minimal, I made the window its own DataContext, but originally there is a dedicated view model):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace BindingDataTemplateMWE
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private object selected;
public event PropertyChangedEventHandler PropertyChanged;
public List<object> Items { get; }
public object Selected {
get { return selected; }
set {
selected = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Selected)));
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
Items = new List<object>() { "a string", DateTime.Now, "another string" };
}
}
}
When I select a different item in the list, the desired effect takes place: the UserControl displays the length if a string is selected and the day of week when a DateTime. Still, I get the following binding error when selecting a DateTime after a string:
Length property not found on object of type DateTime.
and conversely, selecting a string after a DateTime yields
DayOfWeek property not found on object of type String.
It is clear that what I am doing is not meant to be done, but I do not know what the correct paradigm is and what happens in the background. Please advise me. Thank you.
I've seen this problem often when creating complex data templates (several levels of nesting) when views are loaded/unloaded. Honestly, some of such errors I am ignoring completely.
In your case something similar happens because you are manipulating DataContext directly. At the moment the new value is set, the previous value is still used in bindings, which monitor for source change and will try to update the target.
In your scenario you don't need this constant monitoring, so an easy fix is to use BindingMode.OneTime:
<DataTemplate DataType="{x:Type system:String}">
<TextBlock Text="{Binding Content.Length, RelativeSource={RelativeSource AncestorType=ContentControl}, Mode=OneTime}" />
</DataTemplate>
Hello stackoverflowers.
I would like to display dynamically some elements on the screen. I have a OverlayElement base class, and some children classes. The OverlayElement base class contains a FrameworkElement that correspond to a small usercontrol that defines how to draw my OverlayElement.
I have an OverlayViewModel which contain a collection of OverlayElements, binded to an Itemcontrol in the View.
Here are excerpts of OverlayElement and a child.
public abstract class OverlayElement : INotifyPropertyChanged
{
public UserControl View;
}
public class ImageOverlayElement : OverlayElement
{
public ImageOverlayElement(Point start, Point end)
{
View = new ImageOverlayElementView();
}
}
Here is an exemple of ImageOverlayElementView
<UserControl x:Class="Client.ImageOverlayElementView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Client"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
d:DataContext="{d:DesignInstance local:ImageOverlayElement}">
<Grid>
<Image
Source="{Binding ImageSource}"
Height="{Binding Height}"
Width="{Binding Width}"/>
</Grid>
</UserControl>
And this is how i try to use these elements. My problem is that i don't know how to insert my UserControl View from OverlayElement (initialized in the child class) :
<ItemsControl
ItemsSource="{Binding OverlayElementsList}"
Background="Transparent">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type elements:OverlayElement}">
<!-- Need help for here, how can I insert my UserControl View from OverlayElement ? (initialized in the child class) -->
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
You can simply put the view in ContentControl:
<DataTemplate DataType="{x:Type local:OverlayElement}">
<ContentControl Content="{Binding View}" />
</DataTemplate>
But make sure View is a property, otherwise it won't work with data binding.
I am new to WPF and MVVM and trying to follow this design, I have created a window with multiple user controls (10 of each) on it. These user controls will hold a value that should be able to be entered by the User and sent back to the Database.
The issue I have is I am creating the User Controls Pragmatically in a canvass and do not know how to use these instances to set the values on the control from my View Model where I have a SaveMethod that is binded to a Save Button to save the data into the Database. Thanks for the help.
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ClientRatesViewModel viewModel = new ClientRatesViewModel();
DataContext = viewModel;
viewModel.GetChargeUnits();
int previousTopPreRate = 10;
foreach (var rate in viewModel.ClientRatesPreAwr)
{
PreAwr preAwr = new PreAwr();
preAwr.tbPreAwrRate.Text = rate.ClientRatesPreAwr;
PreRatesCanvas.Children.Add(preAwr);
preAwr.Width = 500;
Canvas.SetLeft(preAwr, 10);
Canvas.SetTop(preAwr, previousTopPreRate + 10);
previousTopPreRate += +30;
}
int previousTopPostRate = 10;
foreach (var rate in viewModel.ClientRatesPostAwr)
{
PostAWR postAwr = new PostAWR();
postAwr.tbPostAwrRate.Text = rate.ClientRatesPostAwr;
PostRatesCanvas.Children.Add(postAwr);
postAwr.Width = 500;
Canvas.SetLeft(postAwr, 10);
Canvas.SetTop(postAwr, previousTopPostRate + 10);
previousTopPostRate += +30;
}
}
}
ItemsControl XAML:
<ItemsControl Name="icPreAwr" Margin="10,46,10,10">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ClientRatesPreAwr }" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Technically your are beginning right because you are defining the view and the datacontext. It is recommended to use XAML but if you are beginning there is no issue and no contradiction with MVVM because your code behind is View Code behind.
But here begins the problems, for instance instead of using a foreach, you should use a ItemsControl with an itemtemplate that will be a PreAwr and ItemsSource ClientRatesPreAwr. That will feed your itemscontrol and fill with the PreAwr by itseld PreAwr UserControl has a tbPreAwrRate set the content to {Binding ClientRatesPreAwr} and it will fill with that value.
If you need to make this by code, the properties of the controls are dependency properties and you can bind them by code using
https://msdn.microsoft.com/en-us/library/cc838207%28v=vs.95%29.aspx
I hope it helps you, I strongly recommend you to make the step to design in XAML if the project rules can be done in that way
Indeed my article could be helpful for you http://bit.ly/1CoYRkQ
For anyone reading this I managed to achieve this by doing the following in my XAML.
<ListBox ItemsSource="{Binding ClientRatesPreAwr}"
KeyboardNavigation.TabNavigation="Continue" Margin="0,58,0,69">
<ItemsControl.ItemContainerStyle>
<Style TargetType="ListBoxItem">
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="2" Focusable="False">
<UserControls:PreAwr />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
I have a general question about data templates in WPF. Let's say I have an abstract class called "Question," and various subclasses like "MathQuestion," "GeographyQuestion," etc. In some contexts, rendering the questions as a "Question" using the "Question" data template is good enough, but let's say that I have a list of random Question objects of varying subclasses that I want to display in-turn. I want to display them to the user using their specific data templates rather than their generic Question data template, but since I don't know that at design time, is there anyway to tell WPF, "hey, here's a list of Quesitons, but use reflection to figure out their specific types and use THAT data template?"
What I've thought of so far: I thought that in addition to having my question collection, I could create another collection of the specific types using reflection and somehow bind that to "blah," then I'd get the desired affect, but you can only bind to DependencyProperties in WPF, so I'm not sure what I'd bind to. I really don't like this idea, and my gut tells me there's a more elegant way to approach this problem.
I'm not looking for specific code here, just a general strategy to accomplish what I'm trying to do. Also, I'm using MVVM for the most part if that helps.
Thanks
I'm thinking something like this should work right out of the box:
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:GenericQuestionViewModel}">
<v:GenericQuestion/>
</DataTemplate>
<DataTemplate DataType="{x:Type tvm:GeographyQuestionViewModel}">
<tv:GeographyQuestion/>
</DataTemplate>
<DataTemplate DataType="{x:Type tvm:BiologyQuestionViewModel}">
<tv:BiologyQuestion/>
</DataTemplate>
</UserControl.Resources>
<ContentControl Content="{Binding QuestionViewModel}">
Edit:
Yes, this definitely should work. Here's a more complete example:
Main View Model
public class MainWindowViewModel : ViewModelBase
{
public ObservableCollection<QuestionViewModel> QuestionViewModels { get; set; }
public MainWindowViewModel()
{
QuestionViewModels = new ObservableCollection<QuestionViewModel>
{
new GenericQuestionViewModel(),
new GeographyQuestionViewModel(),
new BiologyQuestionViewModel()
};
}
}
Question View Models
public abstract class QuestionViewModel : ViewModelBase
{
}
public class GenericQuestionViewModel : QuestionViewModel
{
}
public class GeographyQuestionViewModel : QuestionViewModel
{
}
public class BiologyQuestionViewModel : QuestionViewModel
{
}
Question User Controls
<UserControl x:Class="WpfApplication1.GenericQuestion" ...>
<Grid>
<TextBlock Text="Generic Question" />
</Grid>
</UserControl>
<UserControl x:Class="WpfApplication1.GeographyQuestion" ...>
<Grid>
<TextBlock Text="Geography Question" />
</Grid>
</UserControl>
<UserControl x:Class="WpfApplication1.BiologyQuestion" ...>
<Grid>
<TextBlock Text="Biology Question" />
</Grid>
</UserControl>
Main Window
<Window x:Class="WpfApplication1.MainWindow" ...
Title="MainWindow"
Height="900"
Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type local:GenericQuestionViewModel}">
<local:GenericQuestion />
</DataTemplate>
<DataTemplate DataType="{x:Type local:GeographyQuestionViewModel}">
<local:GeographyQuestion />
</DataTemplate>
<DataTemplate DataType="{x:Type local:BiologyQuestionViewModel}">
<local:BiologyQuestion />
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding QuestionViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
Update
Kyle Tolle pointed out a nice simplification for setting ItemsControl.ItemTemplate. Here is the resulting code:
<ItemsControl ItemsSource="{Binding QuestionViewModels}"
ItemTemplate="{Binding}" />
Generally, if you need to dynamically change the DataTemplate based on some non-static logic, you'd use a DataTemplateSelector. Another option it to use DataTriggers in your DataTemplate to modify the look appropriately.