I have a main window with a TabControl in it. Each tab contains a UserControl associated with it. In one of my UserControl, I have a button. When I click the button, I would like to change the SelectedIndex of the TabControl that is in my main window.
I'm using the MVVM pattern so if possible, I would like to do it in XAML with the Command property on my button.
For example:
<Button Content="Switch Tab" Command="SwitchTabCommand" />
Thanks in advance my fellow programmers!
EDIT:
The window view model:
public class CoolViewModel : BaseViewModel
{
#region Properties
public ObservableCollection<ITabViewModel> Tabs { get; set; }
public ITabViewModel SelectedTab { get; set; }
#endregion
#region Constructor
public CoolViewModel()
{
Tabs = new ObservableCollection<ITabViewModel>
{
new VeryNiceViewModel(),
new VeryNiceViewModel()
};
}
#endregion
}
Here is the code of a UserControl inside a tab:
public class VeryCoolViewModel : BaseViewModel, ITabViewModel
{
#region Properties
public ObservableCollection<Test> Tests { get; set; }
public Test currentSelection { get; set; }
public string TabHeader { get; set; }
#endregion
#region Commands
ICommand GoToOtherTab { get; set; }
#endregion
#region Constructor
public GabaritSelecteurViewModel()
{
Tests = new ObservableCollection<Test>
{
new Test { Title = "Title #1" },
new Test { Title = "Title #2" },
new Test { Title = "Title #3" },
new Test { Title = "Title #4" },
new Test { Title = "Title #5" }
};
TabHeader = "Tests";
GoToOtherTab = new RelayCommand(GoToTab, parameter => true);
}
#endregion
#region Methods
private void GoToTab(object parameter)
{
// I don't know how to tell to the
// parent window to go to the other tab...
}
#endregion
}
And here's the XAML for the UserControl (that is inside the TabControl):
<Button Content="Go to the other tab" Command="{Binding GoToOtherTab}" />
Give the child viewmodel a public property
ICommand SwitchTabCommand { get {} set { /* INPC stuff */ } }
Bind it to the button's Command property in the usercontrol XAML.
The parent viewmodel can assign a command to the property when it creates the child viewmodel. You can bind a parent vm property to SelectedIndex on the tab control, and the command that the parent creates can set the bound parent viewmodel property.
If you're not going full MVVM and there's no child viewmodel for the usercontrol, make the command property a dependency property of the usercontrol, and bind it to a parent viewmodel command property in the window XAML.
Related
I want to change value of ViewModel property (which is binded with DataContext). Extremely easy with classic Events, with Commands it becomes formidable task. This is my code:
public partial class MainWindow : Window
{
ViewModel _vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
_vm.BtnClick = new BtnClick();
DataContext = _vm;
}
}
public class BtnClick : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
Debug.WriteLine(parameter.ToString());
}
}
public class ViewModel
{
public ICommand BtnClick { get; set; }
public string Input { get; set; }
public string Output { get; set; }
}
<StackPanel>
<TextBox Text="{Binding Input}"></TextBox>
<TextBlock Text="{Binding Output}"></TextBlock>
<Button Command="{Binding Path=BtnClick}" CommandParameter="{Binding Input}">Translate</Button>
</StackPanel>
Command properly takes value from TextBox, now i want to do things with this value and save it to Output. And problem is from Command perspective i cannot access both DataContext and ViewModel.
The implementation of any command is usually in a viewmodel.
A framework or helper class is routinely used.
For example:
https://riptutorial.com/mvvm-light/example/32335/relaycommand
public class MyViewModel
{
.....
public ICommand MyCommand => new RelayCommand(
() =>
{
//execute action
Message = "clicked Button";
},
() =>
{
//return true if button should be enabled or not
return true;
}
);
Here, there is an anonymous method with that "clicked button" in it.
This will capture variables in the parent viewmodel.
You may therefore set a public property in the viewmodel that's bound to the text property in your view.
For the view to respond you will need to implement inotifypropertychanged and raise property changed in the setter of that public property.
https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-property-change-notification.
From the above.
If PersonName was bound to a textblock in the view.
public string PersonName
{
get { return name; }
set
{
name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged();
}
}
In the command you can do:
PersonName = "Andy";
Which calls the setter of PersonName and a textblock bound to PersonName will read the new value.
I've built WPF app using MVVM design with some simple navigation. I added two Views(User Controls) and dummy ViewModels (empty classess) to the project to switch them through this navigation. Here is my folder structure:
Folder Structure
So when I click i.e. on the first button in navi instead of the view of user control, I got the name of the ViewModel like this:
App screen
(View.ViewModels.NameoftheViewModel)
Here is how I implemented this:
Window.Resources with DataTemplate
Navi buttons for switching user controls
MainWindow.xaml.cs :
public MainWindow()
{
InitializeComponent();
this.DataContext = new NavigationViewModel();
}
NavigationViewModel used for switching :
class NavigationViewModel : INotifyPropertyChanged
{
public ICommand CurrencyCommand { get; set; }
public ICommand GoldCommand { get; set; }
private object selectedViewModel;
public object SelectedViewModel
{
get { return selectedViewModel; }
set { selectedViewModel = value; OnPropertyChanged("SelectedViewModel"); }
}
public NavigationViewModel()
{
CurrencyCommand = new BaseCommand(OpenCurrency);
GoldCommand = new BaseCommand(OpenGold);
}
public void OpenCurrency(object obj)
{
SelectedViewModel = new CurrencyViewModel();
}
public void OpenGold(object obj)
{
SelectedViewModel = new GoldViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
BaseCommand class simply implements ICommand interface.
XAML for showing user controls :
<ContentControl Margin="45, 50, 0, 0" Content="{Binding SelectedViewModel}"/>
Does anyone knows why is this happening? I did this from the tutorial: Tutorial link and it works all fine for this tutorial case but in my project it does not.
from linked tutorial:
Create a DataTemplate of type EmployeeViewModel and it should contain EmployeeView UserControl inside it. Important thing is you should specify any key to the DataTemplates, since these DataTemplates are going to get queried by their DataType.
you should not specify x:Key for DataTemplates for your view models. DataTemplates can be picked by DataType only if there is no x:Key
<DataTemplate DataType="{x:Type vm:CurrencyViewModel}">
<vs:CurrencyControl/>
</DataTemplate>
I create a simple Treeview that I bound to an ObservableCollection.
ObservableCollection<IMarketDataViewModel> MarketDataItems;
public interface IMarketDataViewModel
{
string Title { get; }
ObservableCollection<IMarketDataViewModel> Items { get; set; }
}
public MarketDataUserControl(IMarketDataViewer viewModel)
{
InitializeComponent();
DataContext = viewModel;
marketDataTreeView.ItemsSource = viewModel.MarketDataItems;
}
When I update data in my ViewModel, I only see the first level in my Treeview. The only way I found to resolve the problem is to create an event in my ViewModel and when the data is updated instead calling PropertyChange on MarketDataItems, I trigger the event and the View reset marketDataTreeView.ItemsSource like this :
private void ViewModelOnOnUpdateItems()
{
marketDataTreeView.ItemsSource = null;
marketDataTreeView.ItemsSource = viewModel.MarketDataItems;
}
And this work perfectly --> All levels are displayed.
Someone know why the PropertyChange doesn't work and why I have to reset the ItemsSource ?
I think you should implement a binding to the ItemSource and this is done by a property:
// Create property
public ObservableCollection<IMarketDataViewModel> MarketDataItems { get; private set; }
...
// Create Binding
Binding bindingObject = new Binding("MarketDataItems");
bindingObject.Source = this; //codebehind class instance which has MarketDataItems
marketDataTreeView.SetBinding(TreeView.ItemsSource, bindingObject);
Or the binding in XAML:
<TreeView x:Name="marketDataTreeView" ItemsSource="{Binding Path=MarketDataItems}"/>
Finally the issue is that I didn't call OnPropertyChanged("Items")
public class MarketDataViewModelBase : IMarketDataViewModel, INotifyPropertyChanged
{
.....
private ObservableCollection<IMarketDataViewModel> items;
public ObservableCollection<IMarketDataViewModel> Items
{
get { return items; }
set
{
items = value;
OnPropertyChanged("Items"); //Add this line fix my issue
}
}
}
In my program I have tabItems that have their commands bound to a View Model. I am in the process of implementing a function that will copy the design structure of a "master" tabItem, along with it's command functionality in order to create a new tabItem. I need to do this because the user of this program will be allowed to add new tabItems.
Currently I am using the question Copying a TabItem with an MVVM structure, but I seem to be having trouble when the function tries to copy the Grid object using dependencyValue.
The class I am using:
public static class copyTabItems
{
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) })
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd
where dpd != null
select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly)
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
}
When dependencyValue gets to {[Content, System.Windows.Controls.Grid]} the program throws an InvalidOperationException was Unhandled stating that, "Specified element is already the logical child of another element. Disconnect it first".
What does this mean? Is this a common problem with the Grid in WPF (am I breaking some rule by trying to do this?)? Is there something in my program that I am not aware of that is causing this?
Ok. This is how you're supposed to deal with a TabControl in WPF:
<Window x:Class="MiscSamples.MVVMTabControlSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MiscSamples"
Title="MVVMTabControlSample" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Tab1ViewModel}">
<!-- Here I just put UI elements and DataBinding -->
<!-- You may want to encapsulate these into separate UserControls or something -->
<StackPanel>
<TextBlock Text="This is Tab1ViewModel!!"/>
<TextBlock Text="Text1:"/>
<TextBox Text="{Binding Text1}"/>
<TextBlock Text="Text2:"/>
<TextBox Text="{Binding Text2}"/>
<CheckBox IsChecked="{Binding MyBoolean}"/>
<Button Command="{Binding MyCommand}" Content="My Command!"/>
</StackPanel>
</DataTemplate>
<!-- Here you would add additional DataTemplates for each different Tab type (where UI and logic is different from Tab 1) -->
</Window.Resources>
<DockPanel>
<Button Command="{Binding AddNewTabCommand}" Content="AddNewTab"
DockPanel.Dock="Bottom"/>
<TabControl ItemsSource="{Binding Tabs}"
SelectedItem="{Binding SelectedTab}"
DisplayMemberPath="Title">
</TabControl>
</DockPanel>
</Window>
Code Behind:
public partial class MVVMTabControlSample : Window
{
public MVVMTabControlSample()
{
InitializeComponent();
DataContext = new MVVMTabControlViewModel();
}
}
Main ViewModel:
public class MVVMTabControlViewModel: PropertyChangedBase
{
public ObservableCollection<MVVMTabItemViewModel> Tabs { get; set; }
private MVVMTabItemViewModel _selectedTab;
public MVVMTabItemViewModel SelectedTab
{
get { return _selectedTab; }
set
{
_selectedTab = value;
OnPropertyChanged("SelectedTab");
}
}
public Command AddNewTabCommand { get; set; }
public MVVMTabControlViewModel()
{
Tabs = new ObservableCollection<MVVMTabItemViewModel>();
AddNewTabCommand = new Command(AddNewTab);
}
private void AddNewTab()
{
//Here I just create a new instance of TabViewModel
//If you want to copy the **Data** from a previous tab or something you need to
//copy the property values from the previously selected ViewModel or whatever.
var newtab = new Tab1ViewModel {Title = "Tab #" + (Tabs.Count + 1)};
Tabs.Add(newtab);
SelectedTab = newtab;
}
}
Abstract TabItem ViewModel (you to derive from this to create each different Tab "Widget")
public abstract class MVVMTabItemViewModel: PropertyChangedBase
{
public string Title { get; set; }
//Here you may want to add additional properties and logic common to ALL tab types.
}
TabItem 1 ViewModel:
public class Tab1ViewModel: MVVMTabItemViewModel
{
private string _text1;
private string _text2;
private bool _myBoolean;
public Tab1ViewModel()
{
MyCommand = new Command(MyMethod);
}
public string Text1
{
get { return _text1; }
set
{
_text1 = value;
OnPropertyChanged("Text1");
}
}
public bool MyBoolean
{
get { return _myBoolean; }
set
{
_myBoolean = value;
MyCommand.IsEnabled = !value;
}
}
public string Text2
{
get { return _text2; }
set
{
_text2 = value;
OnPropertyChanged("Text2");
}
}
public Command MyCommand { get; set; }
private void MyMethod()
{
Text1 = Text2;
}
}
Edit: I forgot to post the Command class (though you surely have your own)
public class Command : ICommand
{
public Action Action { get; set; }
public void Execute(object parameter)
{
if (Action != null)
Action();
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged;
public Command(Action action)
{
Action = action;
}
}
And finally PropertyChangedBase (just a helper class)
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Result:
Basically, each Tab Item type is a Widget, which contains its own logic and Data.
You define all logic and data at the ViewModel or Model level, and never at the UI level.
You manipulate the data defined in either the ViewModel or the Model level, and have the UI updated via DataBinding, never touching the UI directly.
Notice How I'm leveraging DataTemplates in order to provide a specific UI for each Tab Item ViewModel class.
When copying a new Tab, you just create a new instance of the desired ViewModel, and add it to the ObservableCollection. WPF's DataBinding automatically updates the UI based on the Collection's change notification.
If you want to create additional tab types, just derive from MVVMTabItemViewModel and add your logic and data there. Then, you create a DataTemplate for that new ViewModel and WPF takes care of the rest.
You never, ever, ever manipulate UI elements in procedural code in WPF, unless there's a REAL reason to do so. You don't "uncheck" or "disable" UI Elements because UI elements MUST reflect the STATE of the data which is provided by the ViewModel. So a "Check/Uncheck" state or an "Enabled/Disabled" state is just a bool property in the ViewModel to which the UI binds.
Notice how this completely removes the need for horrendous winforms-like hacks and also removes the need for VisualTreeHelper.ComplicateMyCode() kind of things.
Copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.
This question is a follow up to this question. I've taken some advice from this site and decided to start learning MVVM implementation for my work with TreeViews. With that being said I am VERY new to MVVM and I'm still getting familiar with the syntax and implementation.
I have a TreeView that displays integer-type data but I would like it to work with strings instead. The tree also allows the user to add to any level by selecting the TreeViewItem and then typing in the new integer header into the textBox, and then clicking a button.
I'd like for a parent, child, and grandchild to be available at start-up with pre-defined names. Another thing to note is that that user will only be able to add to the TreeView at the level of the child (so they'll only be able to add grandchildren).
Model
public class TreeViewModel : PropertyChangedBase
{
public string Value { get; set; }
public ObservableCollection<TreeViewModel> Items { get; set; }
public CollectionView ItemsView { get; set; }
public TreeViewModel(string value)
{
Items = new ObservableCollection<TreeViewModel>();
ItemsView = new ListCollectionView(Items)
{
SortDescriptions =
{
new SortDescription("Value",ListSortDirection.Ascending)
}
};
Value = value;
}
}
ViewModel
public class SortedTreeViewWindowViewModel : PropertyChangedBase
{
private string _newValueString;
public string NewValueString
{
get { return _newValueString; }
set
{
_newValueString = value;
OnPropertyChanged("NewValueString");
}
}
public TreeViewModel SelectedItem { get; set; }
public ObservableCollection<TreeViewModel> Items { get; set; }
public ICollectionView ItemsView { get; set; }
public SortedTreeViewWindowViewModel()
{
Items = new ObservableCollection<TreeViewModel>();
ItemsView = new ListCollectionView(Items) { SortDescriptions = { new SortDescription("Value", ListSortDirection.Ascending) } };
}
public void AddNewItem()
{
ObservableCollection<TreeViewModel> targetcollection;
//Insert the New Node as a Root node if nothing is selected.
targetcollection = SelectedItem == null ? Items : SelectedItem.Items;
if (_newValueString != null)
{
targetcollection.Add(new TreeViewModel(_newValueString));
NewValueString = string.Empty;
}
}
}
View Code-Behind
public partial class Window1 : Window
{
public SortedTreeViewWindowViewModel ViewModel { get { return DataContext as SortedTreeViewWindowViewModel; } set { DataContext = value; } }
public Window1()
{
InitializeComponent();
ViewModel = new SortedTreeViewWindowViewModel()
{
Items = {new TreeViewModel("Test")}
};
}
private void AddNewItem(object sender, RoutedEventArgs e)
{
ViewModel.AddNewItem();
}
private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
ViewModel.SelectedItem = e.NewValue as TreeViewModel;
}
}
Thanks so much for the help. I'm hoping that going through this will help me understand how to edit and build ViewModels so that I can learn to improvise a little more in the future.
UPDATE
The TreeView is now composed of strings, so that part is solved. I still need help with the default nodes though. I have updated my code to reflect this change.
Here are my suggestions
Change the places where you have int to string. The TreeView should handle that change.
In the constructor of your ViewModel, manually insert your default nodes. Make sure you understand how to work with a TreeView as that will affect the design of your Model and ViewModel and naturally improve the implementation.
Here is a very simple example how to fill a tree in the ViewModel which is bound to a TreeView in the View:
View
<Window x:Class="TreeViewExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<StackPanel>
<TreeView ItemsSource="{Binding Tree}"/>
</StackPanel>
</StackPanel>
</Window>
View Code-Behind
namespace TreeViewExample
{
using System.Windows;
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = new MainWindowViewModel();
InitializeComponent();
}
}
}
ViewModel
namespace TreeViewExample
{
using System.Collections.ObjectModel;
using System.Windows.Controls;
class MainWindowViewModel
{
public ObservableCollection<TreeViewItem> Tree { get; set; }
public MainWindowViewModel()
{
Tree = new ObservableCollection<TreeViewItem>();
Tree.Add(GetLoadedTreeRoot());
}
private TreeViewItem GetLoadedTreeRoot()
{
TreeViewItem parent = new TreeViewItem() { Header = "Parent" };
TreeViewItem child1 = new TreeViewItem() { Header = "Child 1" };
TreeViewItem child2 = new TreeViewItem() { Header = "Child 2" };
TreeViewItem grandchild1 = new TreeViewItem() { Header = "Grandchild 1" };
TreeViewItem grandchild2 = new TreeViewItem() { Header = "Grandchild 2" };
child1.Items.Add(grandchild1);
child2.Items.Add(grandchild2);
parent.Items.Add(child1);
parent.Items.Add(child2);
return parent;
}
}
}
Produces:
Parent
Child 1
Grandchild 1
Child 2
Grandchild 2
Additional thoughts:
To clean up your code-behind, you might look up a Command implementation, of which there are many. Although you sometimes need it, avoid code in the code-behind when possible. I really like this example because it shows you a general MVVM implementation without getting into advanced Command-related topics (ItemTemplates, Interactivity namespace, etc.).