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>
Related
I feel though i may be missing somethig here, or its not doable (which i find hard to belive).
I have a UserControl thats using MVVM archtitecture.
The UserControl looks like this.
public partial class UserControl1 : UserControl
{
private string _labelContents;
public UserControl1()
{
InitializeComponent();
DataContext = new UserControl1_VM();
}
}
The VM looks like this.
public class UserControl1_VM : INotifyPropertyChanged
{
private string _labelContents = "Set from VM";
public string LabelContent
{
get => _labelContents;
set { _labelContents = value; OnPropertyChanged("LabelContent"); }
}
public event PropertyChangedEventHandler? PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I want to be able to put in a MainView or similar this:
<mv:UserControl1 LabelContent="My Text"></mv:UserControl1>
But VS states that it "Cannot relsolve symbol LabelContent". Which is understandable as its in the view model. Without putting that Property in to the code behind in the UserControl, and passing the value through it seems impossible. I may just be looking for the wrong thing.
This is a very basic exaple, but LabelContent i think needs to be a dependancy properties because it is bound to it self i.e ulimately.
<mv:UserControl1 LabelContent="{Binding LabelText}"></mv:UserControl1>
Any help with this would be great, as it has me scratching my head, and making me bald!!
Just to let you know if its not a Dependancy Property this works, but seems very clumsy.
Cheers
James
you don't need any view models for UserControl, just a dependency property:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public string LabelContent
{
get { return (string)GetValue(LabelContentProperty); }
set { SetValue(LabelContentProperty, value); }
}
public static readonly DependencyProperty LabelContentProperty = DependencyProperty.Register
(
nameof(LabelContent),
typeof(string),
typeof(UserControl1),
new PropertyMetadata(String.Empty)
);
}
then put it in a view and assign or bind property:
<mv:UserControl1 Name="uc1" LabelContent="My Text"/>
<mv:UserControl1 Name="uc2" LabelContent="{Binding Path=LabelContent, ElementName=uc1}"/>
I have a UserControl Person_UC and Student_UC. There is a ComboBox in Student_UC which I want to disable it from Person_UC.
But its not working. I want to accomplish this without MVVM.
public partial class Person_UC : UserControl
{
public Person_UC()
{
InitializeComponent();
Student_UC su = new Student_UC();
su.myComboBoxName.IsEnabled = false;
}
}
Without MVVM it would be quite hard to solve. You have to manipulate the same instance of Student_UC which is currently used.
Actually, you're instantiating a new Student_UC and disabling its ComboBox, but you're not doing anything with your variable "su". Did you assign it somewhere?
Basically, you should have one ViewModel per UserControl, so a ViewModel for Person_UC and a ViewModel for Student_UC.
Warning, this solution requires you to use a MVVM Framework like MVVM Light (https://mvvmlight.codeplex.com) for sending messages.
One standard way would be sending a message. Bind your Loaded event of your Person_UC to a method in your code-behind like so:
<UserControl x:Class="YourAssembly.Person_UC"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Person_UC"
Loaded="Loaded">
<Grid Name="RootGrid">
</Grid>
</UserControl>
And:
public partial class Person_UC : UserControl
{
public Person_UC()
{
InitializeComponent();
}
private void Loaded(object sender, RoutedEventArgs e)
{
//Use a Message to notify your Student_UC's ViewModel that you would like to disable its ComboBox
Messenger.Default.Send<ChangeComboBoxEnabilityMessage>(new ChangeComboBoxEnabilityMessage(false));
}
}
Then, when you receive the message within the Student_UC's ViewModel, you have to pass this information to the view. Basically, you can bind IsEnable property of the ComboBox to a property in its ViewModel, that you will set to false when the message is received.
public class Student_UC_ViewModel : INotifyPropertyChanged
{
public Student_UC_ViewModel()
{
//Register your message
Messenger.Default.Register<ChangeComboBoxEnabilityMessage>(message => ComboBoxIsEnabled = message.ComboBoxIsEnabled);
}
private bool _comboBoxIsEnabled;
public bool ComboBoxIsEnabled
{
get
{
return _comboBoxIsEnabled;
}
set
{
_comboBoxIsEnabled = value;
OnPropertyChanged("ComboBoxIsEnabled");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
And your message class:
public class ChangeComboBoxEnabilityMessage : MessageBase
{
public ChangeComboBoxEnabilityMessage(bool comboBoxEnabled)
{
ComboBoxIsEnabled = comboBoxEnabled;
}
public bool ComboBoxIsEnabled
{
get;
set;
}
}
I let you bind your IsEnable property of your ComboBox in your Student_UC xaml to the property of its ViewModel (i.e ComboBoxIsEnabled).
Don't forget to make sure your DataContext is set:
public partial class Student_UC : UserControl
{
public Person_UC()
{
InitializeComponent();
DataContext = new Student_UC_ViewModel();
}
...
}
Also, take care of your binding issues, see your output console in Visual Studio.
I have a listbox which i want to get updated when the items get added to a list. I understand I need to bind the listbox. I was trying to follow this question/answer.
I have a ViewModel which handles the list:
namespace TESTS
{
public class ViewModel : INotifyPropertyChanged
{
private List<Cars> _listCars;
public List<Cars> listCars
{
get
{
return _listCars;
}
set
{
if (_listCars == value)
{
return;
}
this.RaisePropertyChanged("Message");
_listCars = value;
this.RaisePropertyChanged("Message");
}
}
public ViewModel()
{
listCars = new List<Cars>();
}
protected void RaisePropertyChanged(string propertyName)
{
Debug.WriteLine("Property Changed");
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Here is the class Cars:
public class Cars: INotifyPropertyChanged
{
public string model{ get; set; }
public string year{ get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
So I did the binding of listbox to the property path in my Viewmodel which is listCars.
<ListBox .... ItemsSource="{Binding listCars}">
So when in my Main.xaml.cs. I do a button click and add the item. It does not get added to the listbox even though its bind to the list on view model.
public sealed partial class MainPage : Page
{
public static ViewModel vm = new ViewModel();
public MainPage()
{
this.InitializeComponent();
this.DataContext = vm;
}
private void button_Click(object sender, RoutedEventArgs e)
{
Cars x = new Cars();
x.model = "Ford";
x.Year = "1998";
vm.listCars.Add(x);
}
}
I hope I explained what i implemented well enough. Is there something wrong in my implementation of ViewModel. I am new to MVVM. Please help.
Use ObservableCollection<T>, not List<T>. The former is designed to be used with MVVM, the latter is not. You'll get all your notifications automatically. It's doable with List<T>, but you'll have to write much more code and the performance will be much worse, especially with big collections. Just don't do it.
If you create the collection in the constructor, assign it to a read-only property and never change its instance (and this is the way you should do it), you don't even need to implement INPC.
When implementing INPC, you're expected to call RaisePropertyChanged after you've changed the property, once, and with the property name that has been changed, not a random unrelated string.
I'm creating a small WPF application. I have to follow MVVM pattern to which I'm new.
I have two views(A,B) and two viewmodels.
This is my scenario, I have a radiobutton in window A. After checking the radiobutton, I click next in window A. The event will close Window A and open window B.
In the constructor of viewmodel of window B, I need to know if Radiobutton in window A is checked or not. How do I do this?
If you "have to follow MVVM pattern", you "have to" do the following.
First, you need create two ViewModels, for view A and for view B (but if you want - you can left only one ViewModel). For ViewModelA you should create ICommand property, that you bind with next button, and bool property IsChecked. ViewModelB should either contain a constructor, which will accept the bool parameter, or a property, which should be updated before view B will show.
Here the example of how it can be accomplished:
public class ViewModelA : ViewModelBase
{
public ViewModelA()
{
_nextCommand = new Cmd(this);
}
public ICommand NextCommand { get { return _nextCommand; } }
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
RaisePropertyChanged("IsChecked");
}
}
private ICommand _nextCommand;
private bool _isChecked;
}
In the *.xaml view for A you should have the following:
<RadioButton IsChecked="{Binding Path=IsChecked}" />
<Button Content="Next" Command="{Binding Path=NextCommand}" />
Please note: DataContext for that view should be of type ViewModelA.
For ViewModelB:
public class ViewModelB : ViewModelBase
{
private bool _isAChecked;
public bool IsAChecked
{
get
{
return _isAChecked;
}
set
{
_isAChecked = value;
RaisePropertyChanged("IsAChecked");
}
}
}
public class Cmd : ICommand
{
ViewModelA _vmA;
public Cmd(ViewModelA vmA)
{
_vmA = vmA;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
var vmB = new ViewModelB();
vmB.IsAChecked = _vmA.IsChecked;
// after that create ViewB, and set its DataContext to vmB
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Instead of creating for each command its class, you can see the examples of the RelayCommand, or DelegateCommand, which are approximately do the same things.
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.