Pass parameter to UserControl in XAML - c#

I'm using Syncfusion's DockingManager with the Adapter for MVVM so a List of ViewModels which implement IDockElement can be bound.
I'm using it this way:
<dm:DockingAdapter Grid.Row="1" ItemsSource="{Binding DockingItems}" />
In addition I'm using the latest version of Catel and its Custom controls.
The MVVM Adapter of the DockingManager needs these entries, to create a view from a ViewModel:
<DataTemplate DataType="{x:Type local:EventEditorViewModel}">
<Grid>
<events:EventEditorControl/>
</Grid>
</DataTemplate>
The problem is, that my EventEditorControl has a parameter in its constructor which needs the ViewModel.
How do I pass the correct ViewModel?

You cannot pass parameters to constructors using xaml.
Make you parameter a dependency property:
<events:EventEditorControl MyParameterAsDependencyProperty="{Binding SomeProperty}" />

Related

WPF: binding through custom dependency property

I have an TabControl bound to a Dictionary and has a custom control as it's ContentTemplate. The custom control has a custom dependency property Schedules and it's DataContext is bound to a ViewModel, Here is how it look like:
Main control:
<TabControl Grid.Row="1" ItemsSource="{Binding Schedules}">
<TabControl.ContentTemplate>
<DataTemplate>
<TabControl >
<TabItem Header="Scheduled flights">
<views:MyViewer Schedules="{Binding Value}"/>
</TabItem>
</TabControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
In MyViewer, I also have an DataGrid that I want it to be bound the Schedules passed from the TabControl, but in the same time MyViewer has a ViewModel assigned to it. This is how it looks like in MyViewer:
<DataGrid Grid.Row="1" ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=Schedules}" AutoGenerateColumns="False" >
So far this is not working, when MyViewer is loaded Schedules property is null. But even if it works, I would want the Schedules to be passed to the ViewModel not code behind. One idea is to populate the Dictionary with ViewModels of MyViewer, but I do not wish to do this, I only want the Main control to know about details of MyViewer. So any clean idea to solve this?
EDIT:
The proposition above does work after changing to ObservableDictionary, but the question remains, how to have the Schedules in the ViewModel
If MyViewer has it's own ViewModel, you should not rather do hacks like this:
ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl}, Path=Schedules}"
This way MyViewer is tightly coupled to some other control.
Im my opinion, in this case, MyViewer's ViewModel should have it's own property Schedules. How could you keep TabControl ViewModel's Schedules and MyViewer ViewModel's Schedules in sync? It depends on your system, but you could try with this ideas:
Sending ViewModel level messages like in MVVM Light, when adding or removing items. Example in this blog post
Try to implement some kind of store like in NgXs or NgRx in Angular
Maybe you don't need to keep Schedules in sync - depends on your system? :)

Find Listbox in DataTemplate inside ItemsControl

To create my own drophandler I need to get access to the listbox which is inside an ItemsControl.
XAML
<ItemsControl ItemsSource="{Binding Days}" Name="myCalendar" Margin="200,75,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="6" Columns="7">
</UniformGrid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- ItemTemplate -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Date}">
</TextBlock>
<ListBox Name="Scenes" ItemsSource="{Binding Scenes}" dd:DragDrop.IsDragSource="True" dd:DragDrop.IsDropTarget="True" dd:DragDrop.DropHandler="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Run Text="{Binding Path=SlugLine}"/>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
How do I get access or find the ListBox inside the ItemsControl from my ViewModel, not via code behind?
You absolutely do not want the VM knowing about the view. The whole point of MVVM is to decouple the view and the logic.
Instead handle the drop in the code-behind. Some people seem to believe that there should be no code-behind in MVVM, but it's absolutely fine as long as it's specific to the view, and there is no VM logic in there.
Imagine that you have hooked up a completely new view to your VM, say a console based text view. If your view logic remains intact with a completely new view, since it's all in the VM then you're fine. If you have logic in the code-behind that would disappear when you changed views, then you need that logic moved down to the VM.
Drag and drop is fine. You handle the drop in code-behind and then call the VM to do the logic associated with the drop, say via a bound command. If replacing the view with a text view, the drop could be CTRL-V instead, but the same VM command would be called to do the logic associated with the drop.
As mentioned, one way to call the VM from the code-behind would be to have a dependency property on the view that gets bound to a command in the VM, with your code-behind just invoking the command via the property.
A simpler way is to just cast the DataContext to your VM type and call a function directly. A lot of people dislike this since it couples the view to a VM type, but I see no issue with it at all. The view is already coupled to all bound properties on the VM anyway. VM's should be view agnostic, but the view NEEDS to know about the VM in order to be useful.

Don't create new view each time with DataTemplate/DataType

I have something like this:
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type local:VM1}">
<!-- View 1 Here -->
</DataTemplate>
<DataTemplate DataType="{x:Type local:VM2}">
<!-- View 2 here -->
</DataTemplate>
<Window.Resources>
<ContentPresenter Content="{Binding}"/>
</Window>
This will automatically swap out the view as I bind different viewmodels, which is very handy.
However, I have one view with a tabcontrol and many subviews. Each subview has several visual parts that are configured by a custom xml file (complex business case). Each time this view is created, the xml file is parsed which causes a small (1-2 second) delay. It's enough of a delay to be annoying and make the UI feel sluggish.
Is there a way to use the DataTemplate pattern without destroying and recreating the view each time a viewmodel is bound? I'd rather not change the viewmodel if possible.
For this case the easiest solution is to have the two views always there and change which one is visible. You can use a converter to change the visibility based on the type of the data context
<View1 Visibility="{Binding Converter={StaticResource TypeToVisibilityConverter, ConverterParameter=VM1}" />
<View2 Visibility="{Binding Converter={StaticResource TypeToVisibilityConverter, ConverterParameter=VM2}" />
And the converter will check if the type matches with the parameter to return Visible, or Collapsed otherwise.
You could wrap your VM into an additional class. Your DataTemplates will decide on the type of the Wrapper class but the real implementation will be exposer through a property of this Wrapper. When this property will change the DataTemplate wont be reloaded but all the bindings will be refreshed.
Wrapper class:
public class WrapperVM1:INotifyPropertyChanged
{
public Content VM1 { get{...} set{...} }
}
public class WrapperVM2:INotifyPropertyChanged
{
public Content VM2 { get{...} set{...} }
}
Now your data templates will describe wrapper class representations:
<DataTemplate DataType="{x:Type local:WrapperVM1}">
<TextBlock Text={Binding Content.SomPropertyInVM1}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:WrapperVM2}">
<TextBlock Text={Binding Content.SomPropertyInVM2}"/>
</DataTemplate>
As you can see if you substitute the Content property of the wrapper with a new instance of VM this won't recreate the view but all bindings will update. However if you need to switch to other type of VM you will have to substitute the Wrapper class by the appropriate Wrapper.

Bind to a MainPage Property out of DataTemplate

I'm trying to bind an object defined in MainPage (CodeBehind) to a ConverterParameter inside a ListView DataTemplate:
<ListView ...>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text=".. SomeBindingExpression"
FontWeight="{Binding Converter={StaticResource ChangeDateToFontWeightConverter},ConverterParameter={Binding Source=MainPage,Path=Cache}}"/>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Goal is to pass the "Cache" - Object defined in MainPage into the Converter. The obove Code calls the converter properly but the passed object Parameter is null. Is there any way to refer the Cache object in this XAML Code?
The ConverterParameter takes a string and can not be databound at all.
You can add a custom property to the converter class and pass your "Cache"-object in the code-behind of your MainPage.
((ChangeDateToFontWeightConverter)this.Resources["ChangeDateToFontWeightConverter"]).MyCustomProperty = myCacheObject;
In general i would advise to take an MVVM approach and extend your databound class with the required data from the cache object (if possible). That way you won't need the converter at all.

Binding to XAML resource

I am creating a page-based WPF application using MVVM. I have created a custom (non dependency object) helper class to centralize navigation. This class is created as a resource of my main window like so.
<Window.Resources>
<local:NavigationManager x:Key="NavigationManagerKey" x:Name="NavigationManager"/>
</Window.Resources>
The class contains an ICommand that I have exposed publicly so that it can be used in XAML. However, I am struggling to find out how to bind to it. I would prefer not to have to set it as the data context for the page as that is already in use. Normally, I bind to a command like so (when I am binding to a command on the data context)
<Button Header="Image" Command="{Binding CreateImageAssetCommand}"></Button>
Thanks for any help with the matter.
You can set the source of the binding:
<Button Header="Image" Command="{Binding CreateImageAssetCommand, Source={StaticResource NavigationManagerKey}}"></Button>

Categories

Resources