WPF MVVM DataTemplate: Inject Template ViewModel with Data from parent Viewmodel - wpf

I have an ObservableCollection in my parent ViewModel which I want to display in the parent View. So I've defined a child View and a child ViewModel handling button clicks.
How do I get each item from the ObservableCollection into the according child ViewModel without loosing my already setup RelayCommands for button click handling?
In my parent View Code-Behind the only thing I do is setting the DataContext to the appropiate ViewModel:
DataContext = new ParentViewModel();
In my parent View XAML I defined a ListBox to display a DataTemplate of my child View:
<ListBox
ItemsSource="{Binding Path=Items}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type vm:ChildViewModel}">
<views:ChildView Width="auto" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now in my ChildView I got several TextBlocks displaying the Binding Data and Buttons which should execute a file in a path specified within the ObservableCollection:
<TextBlock
Text="{Binding Path=Title}" />
...
<Button
Content="Start exe"
Tag="{Binding Path=ExePath}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding Path=OnButtonClicked}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
The Child ViewModel holds RelayCommands to handle the Button click event:
private RelayCommand onButtonClicked;
public ICommand OnButtonClicked
{
get
{
return onButtonClicked ??
(onButtonClicked =
new RelayCommand(ObeyOnButtonClicked, CanObeyOnButtonClicked));
}
}
private void ObeyOnButtonClicked()
{
... //Path conversion
Process.Start(pathToExe);
}
private bool CanObeyOnButtonClicked()
{
return true;
}
Now, within my child View's Code-Behind, when I add
DataContext = new SampleItemViewModel();
to the Constructor, the Button click is handled but the TextBoxes are all empty.
When i remove this line, the TextBoxes are filled correctly, but the Button Clicks are not handled.
How do I get both features working?
EDIT:
ParentViewModel:
private ObservableCollection<Item> items;
public ObservableCollection<Item> Items
{
get { return items; }
set
{
items= value;
OnPropertyChanged("Items");
}
}
... //Filling the Collection in Constructor
ChildViewModel contains just the above mentioned button click handlers.
EDIT:
I tried several things now but I don't know how to bind the Command from the ChildView to my ChildViewModel WITHOUT setting the DataContext of my ChildView to my ChildViewModel

You can remove the Event Trigger, as Button has a Command property.
<TextBlock Text="{Binding Path=Title}" />
...
<Button
Content="Start exe"
Tag="{Binding Path=ExePath}"
Command="{Binding Path=OnButtonClicked}"
>
</Button>
And set the DataContext :
<ListBox
ItemsSource="{Binding Path=Items}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type vm:ChildViewModel}">
<views:ChildView Width="auto" DataContext="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Related

Binding not working inside a <ItemsControl.ItemTemplate>

I can not get the binding of a text property for a DataTemplate in MVVM design pattern.
To show the problem I expose below a simplification of my problem, where I bind two different view properties to the same model property (aka AnObject.Text).
My code in MainWindow.xaml is:
...
<Button Grid.Row="0" Content="{Binding ButtonText}" />
...
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding MyItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DockPanel>
<Label Content="aaaaa" />
<TextBlock Text="{Binding ItemText}" />
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
...
My code behind in MainWindow.xaml.cs (which sets the same DataContext for Button and every item in <ItemsControl ItemsSource>):
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
My code in MainWindowViewModel.cs is:
...
public ObservableCollection<object> MyItems => MyConverter.GetCollection(MyData.List);
public string ItemText => "dddd"; // this DOES works
public string ItemText => AnObject.Text; // this does NOT work
...
public string ButtonText => AnObject.Text; // this DOES works (note, same object property!)
...
Any idea why my binding inside the DataTemplate does not work?
Thanks in advance!
There are various things to understand here:
Button control will have the DataContext set to MainWindowViewModel instance. This is the reason why ButtonText variable value is getting reflected in Button control text.
For ItemsControl the DataContext is the the same as for the Button, i.e. the MainWindowViewModel instance.
Each item in the ItemsControl ItemsSource acts as a DataContext for the elements in the ItemTemplate, i.e. the DockPanel and its child elements. This is managed automatically by the framework. So essentially you will need a public property named ItemText in the class which will act as a DataContext for Dockpanel.
In your case the ItemText property is not the part of the objects which are in list.

ICommand not working in Silverlight User control

I have a UserControlBase with a grid. The grid contains a column with an Action.
<sdk:DataGridTemplateColumn Header="Action">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<Button Style="{StaticResource DataGridButton}" Command="{Binding Source={StaticResource NewsViewModel}, Path=ModifyNewsCommand}" Content="Modify" />
</StackPanel>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
My problem is with my Command. It is throwing me Technical Error which is my first problem, I don't know how to make this application throw me the real error message.
In the code behind of my user control I registered the events:
protected void RegisterMessages()
{
Messenger.Default.Register<string>(this, "NewNewsBtn_Click", NewNewsBtn_Click);
Messenger.Default.Register<string>(this, "ModifyNewsBtn_Click", ModifyNewsBtn_Click);
}
And in my constructor:
public NewsWindow(int underlyingId)
{
InitializeComponent();
this.RegisterMessages();
viewModel = new NewsViewModel(underlyingId);
ucNewsPanel.DataContext = viewModel;
}
My view model (NewsViewModel)
public ICommand ModifyNewsCommand
{
get
{
return new RelayCommand<string>(e =>
{
Messenger.Default.Send(string.Empty, "ModifyNewsBtn_Click");
});
}
}
What's weird here is that my NewNewsBtn is working, while my ModifyNewsBtn is not.
This button is outside the grid so it might make a difference on why it is working.
<Button x:Name="NewNewsBtn" MaxHeight="50" MaxWidth="100" Command="{Binding Path=NewNewsCommand}" Content="Add New" />
Your DataGrid will be bound to some collection, having a row per each item. Now the item is the DataContext for a row. What you need to do is bind your "Modify"-button to the parent DataContext. If you are using silverlight5 you can use an AncestorBinding:
<Button
Content="Modify"
Command="{Binding
Path=DataContext.ModifyNewsCommand,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
Your syntax looks okay:
<Button Style="{StaticResource DataGridButton}" Content="Modify"
Command="{Binding Source={StaticResource NewsViewModel}, Path=ModifyNewsCommand}"/>
But you set your viewModel in code. Am I right that you create StaticResource in XAML? If it is, then just delete setting DataContext from code-behind cause the following row Command="{Binding Source={StaticResource NewsViewModel}, Path=ModifyNewsCommand}" will see in the another instance of your viewModel. (cause you've create two instances of NewsViewModel as a StaticResource and in code-behind)
Update:
I needed the DataContext in code behind as I am passing a parameter to my view model. Any way I can do that if I removed it from code behind?
Then you should delete StaticResource from Binding of Command:
<Button Style="{StaticResource DataGridButton}" Command="{Binding ModifyNewsCommand}"
Content="Modify"/>
Cause you are referencing to another instance of NewsViewModel.

WPF: binding several views to a TabControl's items

In the current project we work on, we have a main window with several views (each with its own viewmodel) that are presented as items in a tab control. E.g: One tab item is an editor, and contains the editor view as follows:
<TabItem Header="Test Editor">
<TestEditor:TestEditorView DataContext="{Binding TestEditorViewModel}"/>
</TabItem>
Another one shows results:
<TabItem Header="Results Viewer">
<ResultViewer:ResultViewer x:Name="resultViewer1" DataContext="{Binding Path=ResultViewModel}" />
</TabItem>
etc.
I'd like to have the TabItems bound to something in the main window's viewmodel, but I can't figure out how to bind the view's name to any property without breaking the MVVM pattern. I'd like to have something like:
<TabControl.ContentTemplate>
<DataTemplate>
<TestEditor:TestEditorView DataContext ="{Binding TabDataContext}"/>
</DataTemplate>
</TabControl.ContentTemplate>
only with some binding instead of having to know at design time what type will be used as content.
Any ideas?
Usually I have the TabControl's Tabs stored in the ViewModel, along with the SelectedIndex, then I use DataTemplates to determine which View to display
View:
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type ResultViewModel}">
<ResultViewer:ResultViewer />
</DataTemplate>
<DataTemplate DataType="{x:Type EditorViewModel}">
<TestEditor:TestEditorView />
</DataTemplate>
</Window.Resources>
<TabControl ItemsSource="{Binding TabCollection}"
SelectedIndex="{Binding SelectedTabIndex}" />
</Window>
ViewModel:
public class MyViewModel : ViewModelBase
{
publicMyViewModel()
{
TabCollection.Add(new ResultsViewModel());
TabCollection.Add(new EditorViewModel());
SelectedTabIndex = 0;
}
private ObservableCollection<ViewModelBase> _tabCollection
= new ObservableCollection<ViewModelBase>();
public ObservableCollection<ViewModelBase> TabCollection
{
get { return _tabCollection };
}
private int _selectedTabIndex;
public int SelectedTabIndex
{
get { return _selectedTabIndex; }
set
{
if (value != _selectedTabIndex)
{
_selectedTabIndex = value;
RaisePropertyChanged("SelectedTabIndex");
}
}
}
}

wpf Usercontrol template

In my MVVM app I have a treeview representing records in a database. My views and viewmodels are linked in a resource dictionary like this
<DataTemplate DataType="{x:Type vm:TrialSiteViewModel}">
<vw:TrialSiteView />
</DataTemplate>
I want to display a preview of the view when a user hovers over an icon using the tooltip. My HierarchicalDataTemplate in the treeview is this
<HierarchicalDataTemplate DataType="{x:Type vm:TrialSiteViewModel}"
ItemsSource="{Binding Path=Children}">
...
<Button Style="{StaticResource previewButtonStyle}">
<Button.ToolTip>
<ToolTip Style="{x:Null}">
<ToolTip.ContentTemplate>
<DataTemplate>
<localtools:ObjectPreview
PreviewObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=DataContext}"
/>
</DataTemplate>
</ToolTip.ContentTemplate>
</ToolTip>
</Button.ToolTip>
</Button>
</StackPanel>
</HierarchicalDataTemplate>
This correctly picks up the TrialSiteViewModel that is the DataContext for the Treeviewitem.
ObjectPreview uses a viewbox and contentcontrol to display the view of the record
<Viewbox Grid.Row="1" Name="treeviewViewBox"
Stretch="Uniform"
IsEnabled="False">
<ContentControl Name="treeViewItemViewModel"
Content="{Binding PreviewObject}">
</ContentControl>
</Viewbox>
and the code behind contains the dependency property
public partial class ObjectPreview : UserControl
{
public ObjectPreview()
{
InitializeComponent();
}
public static readonly DependencyProperty _previewObjectProperty =
DependencyProperty.Register("PreviewObject", typeof(TreeViewItemViewModel), typeof(ObjectPreview));
public TreeViewItemViewModel PreviewObject
{
get { return (TreeViewItemViewModel)GetValue(_previewObjectProperty); }
set { SetValue(_previewObjectProperty, value); }
}
}
The problem I'm having is that the Template used to display the object is the same as that used in the treeview. This simply shows an icon and an object summary (ie. Primary Key and one or two key fields) rather than the entire template as defined in the view TrialSiteView. If I amend the code to use a button Command on the TrialSiteViewModel and inject it into ObjectPreview I can set the contentcontrol in the code behind and the TrialSiteView is used.
I'm guessing that somehow the Template is inferred from the TreeViewItem. Can anyone tell me how I can ensure the tooltip uses the TrialSiteView?
UPDATE
Ok, so I've fixed this but had to resort to code behind and removed the usercontrol and put the view directly in the tooltip. The key bit is getting the datatemplate from the resources. I'd tried to do this previously by assigning a key to the datatemplate, but either my code was flawed or it did not work. Anyhow, this works but is not the preferred Xaml solution.
private void PreviewObject_MouseEnter(object sender, MouseEventArgs e)
{
Image image = (Image)sender;
var key = new System.Windows.DataTemplateKey(image.DataContext.GetType());
var datatemplate = (DataTemplate)this.FindResource(key);
ToolTip tooltip = new ToolTip();
tooltip.Style = VisualUtils.GetResource<Style>("ControlTemplates.xaml", "toolTipWithContentStyle");
tooltip.MaxWidth = 460;
ContentControl contentcontrol = new ContentControl();
contentcontrol.ContentTemplate = datatemplate;
contentcontrol.Content = image.DataContext as TreeViewItemViewModel;
Viewbox viewbox = new Viewbox();
viewbox.Stretch = Stretch.Uniform;
viewbox.Child = contentcontrol;
tooltip.Content = viewbox;
image.ToolTip = tooltip;
}
What you need to do is to specify explicitly what data template to use. In order to do that just add a template property along with the PreviewObject property in the preview control:
public static readonly DependencyProperty _previewObjectTemplateProperty =
DependencyProperty.Register("PreviewObjectTemplate", typeof(DataTemplate), typeof(ObjectPreview));
public DataTemplate PreviewObjectTemplate
{
get { return (DataTemplate)GetValue(_previewObjectTemplateProperty); }
set { SetValue(_previewObjectTemplateProperty, value); }
}
Then, in the ObjectPreview.xaml add the ContentTemplate property that is bound to the PreviewObjectTemplate property:
<Viewbox Grid.Row="1" Name="treeviewViewBox"
Stretch="Uniform"
IsEnabled="False">
<ContentControl Name="treeViewItemViewModel"
Content="{Binding PreviewObject}"
ContentTemplate="{Binding PreviewObjectTemplate}" >
</ContentControl>
</Viewbox>
And finally, give a key to your data template and specify a reference to it explicitly when you declare ObjectPreview:
<DataTemplate x:Key="FullViewTemplate" DataType="{x:Type vm:TrialSiteViewModel}">
<vw:TrialSiteView />
</DataTemplate>
...
<ToolTip Style="{x:Null}">
<ToolTip.ContentTemplate>
<DataTemplate>
<localtools:ObjectPreview
PreviewObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=DataContext}"
PreviewObjectTemplate="{StaticResource FullViewTemplate}"
/>
</DataTemplate>
</ToolTip.ContentTemplate>

How to fire event from button inside datagrid in silverlight and MVVM

I have button at first column in datagrid. I am using MVVM and try to bind Command to Command in ViewModel but when I click button in each row, it don't work (It don't call Command in ViewModel) but if I move that button out of datagrid it's working properly.
How can I fire event from button inside datagrid in MVVM?
Update 1:
XAML's code is:
<datagrid:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center">
<Button x:Name="button" Content="View" Margin="5" DataContext="{StaticResource XDataContext}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding ViewOrganizationCommand}"
CommandParameter="{Binding ElementName=dtgOrganizations, Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</DataTemplate>
</datagrid:DataGridTemplateColumn.CellTemplate>
ViewModel's code is:
public ViewModelCommand ViewOrganizationCommand { get; set; }
Instead of using EventTrigger, why not simply bind to Button.Command directly, like so?
<Button
...other properties...
Command="{Binding ViewOrganizationsCommand}"
CommandParameter="{Binding}"/>
That will bind the command, and set the CommandParameter to the DataContext of the Button, which presumably is a good thing to bind the parameter to. If it's not, just bind CommandParameter to something else that helps you uniquely identify the specific row being clicked on.
This article brings the solution with DataContextProxy. Applying this solution makes possible to write a button code like Austin Lamb's answer.
The command binding using the Event Trigger looks fine (This is how i always do it),
But I suspect that your command is never assigned to (You are using Automatic Properties)
I usually do it like this:
private ViewModelCommand viewOrganizationCommand;
public ViewModelCommand ViewOrganizationCommand
{
get
{
if (viewOrganizationCommand == null)
viewOrganizationCommand = new ViewModelCommand(Action, CanDoIt);
return viewOrganizationCommand;
}
}
try setting you Button's DataContext to the StackPanel and set the Command and CommandParameter of the Button.
<datagrid:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center" DataContext="{StaticResource XDataContext}">
<Button x:Name="button" Content="View" Margin="5" Command="{Binding ViewOrganizationCommand}" CommandParameter="{Binding ElementName=dtgOrganizations, Path=SelectedItem}" >
</Button>
</StackPanel>
</DataTemplate>
</datagrid:DataGridTemplateColumn.CellTemplate>
I solved this problem by use EventToCommand behavior in MVVMLight Toolkit
Each row of the DataGrid has it's DataContext set to that object. If the ItemSource for the grid is an ObservableCollection, each row's DataContext is the Organization object.
There are two ways to handle this.
Create a wrapping or extension ViewModel that exposes the command. Then the command should fire.Here is some psuedo code.
public class OrganizationExtensionViewModel
{
<summary>
/// Private currentOrginization property.
/// </summary>
private Organization currentOrginization;
public Organization CurrentOrganization
{
get
{
return this.currentOrginization;
}
set
{
if (this.currentOrginization != value)
{
this.currentOrginization = value;
this.RaisePropertyChanged("CurrentOrganization");
}
}
}
public ViewModelCommand ViewOrganizationCommand { get; set; }
public OrganizationExtensionViewModel(Organization o)
{
this.CurrentOrganization = o;
this.ViewOrganizationCommand = new ViewModelCommand(this.ViewOrgnaizationClicked);
}
}
Define the ViewModel in the xaml as a StaticResource and refer to it as the binding path.
...
In the grid
<Button x:Name="button" Content="View" Margin="5" Command="{Binding ViewOrganizationCommand, Source={StaticResource viewModel}}" />

Resources