I'm new to WPF MVVM, and a bit stuck. I need to switch between about 100 different tables on the same view using MVVM with wpf.
I have Treeview with the list of table names and on item selection the correct DataGrid has to be displayed beside the Treeview.
I created Model and ViewModel classes for each table. However, how do I select the right Viewmodel to bind depend on the selection.
If I understand your problem right - then you have a design problem.
First get the SelectedItem of your TreeView
To use the SelectedItem-Binding on a TreeView see this. But you could do also the bad way in the code behind.
Second bind your SelectedItem
So what you want to do is:
bind the SelectedItem on something like a ContentControl or ContentPresenter. Or do it the bad way in the code behind.
For example like this:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TreeView ItemsSource="{Binding MyItemSource}">
<!-- Get the selected item here (watch how to in the linked answer) -->
</TreeView>
<ContentPresenter Grid.Column="1"
Content="{Binding Path=SelectedItem}"
>
<ContentPresenter.ContentTemplate>
<DataTemplate>
<DataGrid>
<!-- Your DatGrids or what ever -->
</DataGrid>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</Grid>
Third (optional) If you have different DataGrids
You could also use a DataTemplateSelector to change your Views depending on your SelectedItem too. You would use it on the ContentPresenter in this example.
Related
I have a ControlTemplate in my App.xaml like bellow:
<ControlTemplate x:Key="MyTempplate">
<ListView x:Name="MyListView"
AutomationId="SelectRelationListViewId"
ItemsSource="{Binding MyListViewData}"
SelectedItem="{Binding MySelectedItem}"
BackgroundColor="White"
RowHeight="60"
SeparatorColor="White"
IsGroupingEnabled="False"
VerticalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="#E7E4E0"
Padding="20,0,20,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Text="{Binding MyName}"
VerticalOptions="CenterAndExpand"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ControlTemplate>
My page is bound with my ViewModel and if I copy/paste the code from my template to my pagem, data and events are correctly handled but nothing happend when I try to use my template, the ListView is empty and the events are not triggered.
I already tried "{TemplateBinding DataContext.MyPropertyNameToBind}".
We need more details on your code and your intention on creating a control template but here are the steps that can help you:
1- First: ControlTemplate utility,
in Xamarin Forms, a 'ControlTemplate' is used to customize appearance of a control. More precisely it defines the layout in which the content will be displayed.
So it should contain a 'ContentPresenter' object.
It can be applied only on 'ContentPage' and 'ContentView' objects
--> On which UI 'Element' are you trying to bind your ControlTemplate to ?
2- Then using TemplateBindings (I suspect a problem here)
If I'm right, TemplateBindings can only be used to bind to 'Parent' Bindable Properties.
When you are doing:
<ControlTemplate x:Key="MyTemplate">
<ListView ItemsSource={TemplateBinding BindingContext} />
...
It's ok because 'BindingContext' is a BindableProperty, but if you do:
<ControlTemplate x:Key="MyTemplate">
<ListView ItemsSource={TemplateBinding BindingContext.MyItems} />
...
It's ko because 'BindingContext.MyItems' is not a BindableProperty (I presume)
3- Finally, what to do ?
Solution 1
Create in your page that owns the templated control, intermediate BindableProperties that bind to your ViewModel.
Then your 'TemplateBindings' should reference these properties.
A good example using 'TemplateBinding' & 'BindableProperty' here:
Xamarin TemplateBinding documentation.
Solution 2
For me, I will prefer to extract to App.xaml,
the ListView DataTemplate to a new data template resource
the Listview properties into a new 'ListView' Style
And in my page I would have:
<Page ...>
<ListView x:Name="myList"
Style="{StaticResource MyListViewStyle}"
ItemsSource="{Binding vmItems}"
DataTemplate="{StaticResource MyListViewDataTemplate}"
/>
In fact you can include the 'DataTemplate' property inside your Style directly...
Let me know if it helps.
My WPF Windows contains a TabControl which displays content on different tabs. A click on the button below executes a method via ICommand interface / Binding. The called method generates text which is intended to be displayed in the second tab.
How can I switch to the second tab on button click without violating the MVVM Pattern?
I tried to bind the TabItem.IsSelected Property to something in my ViewModel but I wanted to use the other tabs (tab1) as well.
Any thoughts?
I found it out by myself.
The key is a two way binding. When the button is clicked it sets the property DisplayXamlTab true. The IsSelected attribute is bound to this variable. if another tab is clicked the binding will set the DisplayXamlTab Property to false.
Note: UpdateSourceTrigger=PropertyChanged is also very important
Code comes below:
XAML:
<TabItem Header="XAML" IsSelected="{Binding DisplayXamlTab, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<Grid Background="#FFE5E5E5">
<TextBox x:Name="TxtXamlOutput" IsReadOnly="True" Text="{Binding XamlText, Mode=TwoWay, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible"/>
</Grid>
</TabItem>
C# Property:
private bool displayXamlTab;
public bool DisplayXamlTab
{
get { return this.displayXamlTab; }
set
{
this.displayXamlTab = value;
this.RaisePropertyChanged("DisplayXamlTab");
}
}
if you're going the MVVM way you're going to create two dependency properties in the code behind:
ObservableCollection<ItemType> Items;
ItemType MySelectedItem;
Then, bind the TabControl ItemsSource property to the Items and bind the SelectedItem property to MySelectedItem
<TabControl ItemsSource="{Binding Items}"
SelectedItem="{Binding MySelectedItem, Mode=TwoWay}">
<TabControl.ItemTemplate>
<DataTemplate>
<... here goes the UI to display ItemType ... >
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
When you want to change the selected tab, simply update the MySelectedItem dependecy property
Although this question is fairly old and well answered already, I thought I'd add this additional answer to demonstrate an alternative way of changing the selected TabItem in a TabControl. If you have a view model for each TabItem, then it can be helpful to have an IsSelected property in it to determine whether it is selected or not. It is possible to data bind this IsSelected property with the TabItem.IsSelected property using the ItemContainerStyle property:
<TabControl ItemsSource="{Binding MenuItems}" TabStripPlacement="Top">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type ControlViewModels:MenuItemViewModel}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageSource}" Margin="0,0,10,0" />
<TextBlock Text="{Binding HeaderText}" FontSize="16" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type ControlViewModels:MenuItemViewModel}">
<ContentControl Content="{Binding ViewModel}" />
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
You can now change the selected TabItem from the parent view model like this:
MenuItems[0].IsSelected = true;
Note that because this property is data bound to the TabItem.IsSelected property, calling this...:
MenuItems[1].IsSelected = true;
... will infact also automatically set the MenuItems[0].IsSelected property to false. so if the view model that you are working with has its IsSelected property set to true, then you can be sure that its related view is selected in the TabControl.
You can create a binding between the view model and the TabControl.SelectedIndex property - i.e., 0 selects the first TabItem , 1 selects the second, etc.
<TabControl DataContext="..." SelectedIndex="{Binding SomeVmProperty}" ...
(alternatively, depending on how you've got things set up, you could bind against SelectedItem...)
You'll likely want to use some sort of "Event Aggregator" pattern (I.e. the Messenger class in MVVM Light) to broadcast some sort of "navigation" message. Your View - the TabControl - can listen for the specific message, and navigate to Tab2 when the message is received.
Alternatively, you can bind the "SelectedItem" property of the TabControl to your ViewModel, and simply call CurrentTab = MySecondTabViewModel from within your VM. This is the approach recommended by #HighPoint in the comments to the OP, but I'm not a fan; see below. Another caveat to this approach is that you need to be familiar with DataTemplates, as you will need to map a view to each ViewModel which you display.
I personally like the first approach, because I don't consider it to be a "responsibility" of the ViewModel to handle tab navigation. If you simply alert your View when data changes in your ViewModel, you allow the View to decide whether or not it wants to change tabs.
I'm interested in creating an app that displays some buttons and changes a viewport according to the selected button. The viewport in my app is a ContentControl and I thought of changing its content whenever a button is clicked. However, I believe there's a better approach, by perhaps injecting the ViewModels of each of the Views I want to present to the ContentControl and styling them using DataTemplates (Since I want to avoid having a grid with many controls and just setting their Visibility property whenever I want to show a particular view). Which of the approaches seems better to you? Do you have a different approach for this?
The view should be something similar to this:
Thanks!
Usually have a ViewModel behind the window which contains:
ObservableCollection<IViewModel> AvailableViewModels
IViewModel SelectedViewModel
ICommand SetCurrentViewModelCommand
I display the AvailableViewModels using an ItemsControl, which has its ItemTemplate set to a Button. The Button.Command is bound to the SetCurrentViewModelCommand, and it passes the current data item from the AvailableViewModels collection in through the CommandParameter
To display the content area, I use a ContentControl with ContentControl.Content bound to SelectedViewModel, and DataTemplates get used to tell WPF how to render each ViewModel.
The end result is my XAML looks something like this:
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModelA}">
<local:ViewA />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelB}">
<local:ViewB />
</DataTemplate>
</Window.Resources>
<DockPanel>
<Border DockPanel.Dock="Left" BorderBrush="Black" BorderThickness="0,0,1,0">
<ItemsControl ItemsSource="{Binding AvailableViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.SetCurrentViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding }"
Margin="2,5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<ContentControl Content="{Binding SelectedViewModel}" />
</DockPanel>
You can view an example of the full code used for such a setup on my blog
I have defined a ItemsControl control in xaml as follows:
<ItemsControl BorderThickness="0" Name="SummaryList">
</ItemsControl>
In the same xaml file, I've defined the following DateTemplate for each item in the ItemsControl:
<DataTemplate DataType="{x:Type app:UpgradeItem}">
<StackPanel Orientation="Horizontal">
<Label Margin="5,5" Content="{Binding Path=ItemText,Mode=OneTime}" />
<Label Margin="5,5" Content="{Binding Path=FromVersion,Mode=OneTime}" />
<Label Margin="5,5" Content="{Binding Path=ToVersion,Mode=OneTime}" />
</StackPanel>
</DataTemplate>
I have bound the ItemsControl's ItemSource to a generlic list of UpgradeItems (i.e. List) and programmatically add to this list during execution. The items in the ItemsControl control populate correctly except that the data in the columns are not aligned. How can I modify this so that the three separate columns (ItemText, FromVersion and ToVersion) are aligned?
TIA
You should be able to use a SharedSize groups on grids, however I feel there is a better way to do this. Here's a link on the subject: http://blogs.microsoft.co.il/blogs/guy/archive/2009/06/30/wpf-grid-shared-size-groups.aspx
Part of what makes this tricky is that the ItemsControl doesn't have "columns": the control itself has no idea how its items' contents are laid out in relation to each other.
Your best bet would probably be to use one of the subclasses of ItemsControl that does support columnar data, such as ListView/GridView or the full-fledged DataGrid.
I have a tree view that is using the Model View architecture, each TreeViewItem has a windows Form attribute, when I click on a node I want the application to display the form associated with that node to the right hand side of the tree.
How can you achieve this using binding I have tried the following but the user control Associated with ApplicationForms doesn't get displayed.
<ContentControl Margin="163,5,127,5" Content="{Binding SelectedItem,ElementName=ApplicationTree}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:ApplicationViewModel}">
<StackPanel>
<TextBlock Text="Displaying an A!" />
<ContentPresenter Name="MyContent">
<ContentPresenter.Content>
<UserControl x:Name="UserCntrl2" HorizontalAlignment="Stretch" Height="Auto" Width="Auto" Content="{Binding ApplicationForms}"/>
</ContentPresenter.Content>
</ContentPresenter>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ApplicationsViewModel}">
<StackPanel>
<TextBlock Text="Displaying a B!" />
<!--<TextBlock Text="{Binding Bar}" />-->
</StackPanel>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
Since you're using MVVM, you can alternativly put that kind of logic in the view model. you can then bind the IsSelected property of tree node to your viewmodel, then when IsSelected get set to true by wpf (when the use selects the item) you can do whatever you want.
Its a very useful pattern to use view models this way. your viewmodels can have references to all kinds of stuff and affect them based on selection or expansion. You can also go the other way around and have code affect the viewmodels and let the databinding update the actual controls
Here is a pretty good article on MVVM and treeview
You should also check out the HierarchicalDataTemplate if you're working with treeviews
-edit-
After reading the question properly, i see that you're already doing the right thing, that is binding your master control to the SelectedItem of the Treeview. I do belive the SelectedItem property points to the TreeViewItem though, not the actual VM. Perhaps thats the problem?