Okay so I've now got a more specific question.
I'm trying to figure out how I can change the value (bool) of a label when two Textboxes are no longer empty. I cannot seem to figure out how to get it to work even though it seems very straight forward.
Could someone point me in the right direction?
Please see below my code.
Model (Person.cs)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PracticeUI.Model
{
public class Person
{
private string _firstName;
private string _lastName;
public string FullName
{
get
{
return _firstName + " " + _lastName;
}
set { }
}
public string FirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
}
}
public string LastName
{
get
{
return _lastName;
}
set
{
_lastName = value;
}
}
}
}
ViewModel (PersonViewModel.cs)
using PracticeUI.Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace PracticeUI.ViewModel
{
public class PersonViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Person _newPerson = new Person();
private ICommand _addPerson;
public Person NewPerson
{
get
{
return _newPerson;
}
set
{
_newPerson = value;
OnPropertyChanged("NewPerson");
}
}
public PersonViewModel()
{
_PersonList.Add(new Person() { FirstName = "Tom", LastName = "Barratt" });
_PersonList.Add(new Person() { FirstName = "Harriet", LastName = "Hammond" });
}
private ObservableCollection<Person> _PersonList = new ObservableCollection<Person>();
public ObservableCollection<Person> PersonList
{
get
{
return _PersonList;
}
set
{
_PersonList = value;
OnPropertyChanged("PersonList");
OnPropertyChanged("AddPersonCanExecute");
}
}
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public ICommand AddPersonCommand
{
get
{
if (_addPerson == null)
{
_addPerson = new RelayCommand(p => this.AddPersonCanExecute, p => this.AddPerson());
}
return _addPerson;
}
}
public bool AddPersonCanExecute
{
get
{
return _newPerson.FirstName != string.Empty || _newPerson.LastName != string.Empty;
}
}
public void AddPerson()
{
_PersonList.Add(new Person() { FirstName = _newPerson.FirstName, LastName = _newPerson.LastName });
OnPropertyChanged("PersonList");
}
}
}
View (MainWindow.xaml)
<Window x:Class="PracticeUI.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ViewModel="clr-namespace:PracticeUI.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ViewModel:PersonViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Source={StaticResource ViewModel}, Path=PersonList}" Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" Height="200" Margin="0 0 0 20">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding FirstName}"/>
<Label Content="{Binding LastName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Label Content="First Name:" Grid.Row="2" Grid.Column="1"/>
<TextBox Text="{Binding Source={StaticResource ViewModel}, Path=NewPerson.FirstName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Grid.Row="2" HorizontalAlignment="Left" Grid.Column="2" Height="40" Width="200" Margin="10 5"/>
<Label Content="First Name:" Grid.Row="3" Grid.Column="1"/>
<TextBox Text="{Binding Source={StaticResource ViewModel}, Path=NewPerson.LastName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Grid.Row="3" HorizontalAlignment="Left" Grid.Column="2" Height="40" Width="200" Margin="10 5"/>
<Button Command="{Binding Source={StaticResource ViewModel}, Path=AddPersonCommand}" Content="Add Person" Width="120" Height="30" Grid.Row="4" Grid.Column="2"/>
<Label Content="{Binding Source={StaticResource ViewModel}, Path=AddPersonCanExecute, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" Grid.Row="2" Grid.Column="4"/>
</Grid>
</Window>
First, rename PersonViewModel to MainViewModel. It's not a viewmodel that represents a person, it's your main viewmodel for the whole program. It has a whole collection of Person; how is it one person? It isn't. Naming your classes well makes it much easier to keep track of what's what. We'll be renaming Person to PersonViewModel because it needs to be a viewmodel also, and it does actually represent a person.
You want the UI to look at the value of AddPersonCanExecute whenever there's a change in the value of NewPerson.FirstName or NewPerson.LastName.
What can cause those values to change?
One way is that NewPerson can change. So:
public Person NewPerson
{
get
{
return _newPerson;
}
set
{
_newPerson = value;
OnPropertyChanged(nameof(AddPersonCanExecute));
OnPropertyChanged(nameof(NewPerson));
}
}
Another way is that the user can type a new value into the textboxes bound to the FirstName and LastName properties of NewPerson. Then you and the UI are out of luck, because Person isn't a viewmodel. It never raises any events when its properties change. So make it a viewmodel.
public class ViewModelBase : INotifyPropertyChanged
{
// Copy your INotifyPropertyChanged implementation here from your main viewmodel
// Make your main viewmodel inherit from ViewModelBase
}
// Formerly PersonViewModel
public class MainViewModel : ViewModelBase
{
// We need this to be the actual type because we'll need to be calling
// RaiseCanExecuteChanged() on it. Or whatever equivalent.
private RelayCommand _addPerson;
// All the stuff PersonViewModel had.
// Stuff
// Stuff
// Stuff
}
// Remember, your old PersonViewModel is now named MainViewModel. This is the class
// that you used to call Person.
public class PersonViewModel : ViewModelBase
{
public string FullName
{
get
{
return _firstName + " " + _lastName;
}
// No empty set, not ever. Somebody will try to set FullName and the compiler
// will let him think it worked. But nothing will change. That's a bug.
//set { }
}
public string FirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
// Do the same for LastName. Careful you don't pass nameof(FirstName)
// over there.
OnPropertyChanged(nameof(FirstName));
OnPropertyChanged(nameof(FullName));
}
}
Now the UI knows when those properties change, but the main viewmodel still doesn't. But now that we have notifications from Person, that's solvable. We have to rewrite NewPerson again:
private PersonViewModel _newPerson = null;
public PersonViewModel NewPerson
{
get { return _newPerson; }
set
{
if (value != _newPerson)
{
// Take the handler off the old NewPerson, if any.
if (_newPerson != null)
{
_newPerson.PropertyChanged -= NewPerson_PropertyChanged;
}
_newPerson = value;
if (_newPerson != null)
{
_newPerson.PropertyChanged += NewPerson_PropertyChanged;
}
OnPropertyChanged(nameof(NewPerson));
OnPropertyChanged(nameof(AddPersonCanExecute));
// I don't know what your RelayCommand class looks like, but it should
// provide some way to force it to raise its CanExecuteChanged event.
// That's what the Button is waiting for to enable or disable itself.
_addPerson.RaiseCanExecuteChanged()
}
}
}
private void NewPerson_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(Person.FirstName):
case nameof(Person.LastName):
OnPropertyChanged(nameof(AddPersonCanExecute));
AddPerson
break;
}
}
Another point: Don't make your viewmodel a resource. It's not breaking your code, but it serves no purpose and creates extra work for you.
<Window.DataContext>
<ViewModel:PersonViewModel />
</Window.DataContext>
<Window.Resources>
<!-- remove it from here -->
</Window.Resources>
Now for all controls belonging to the Window itself, all your bindings can look like this:
<TextBox
Text="{Binding NewPerson.FirstName, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="2"
HorizontalAlignment="Left"
Grid.Column="2"
Height="40" Width="200" Margin="10 5"
/>
Get rid of Mode=TwoWay on TextBox.Text; that property will cause bindings on it to be TwoWay by default. Keep UpdateSourceTrigger=PropertyChanged only on TextBox.Text: That will cause the textbox to update the viewmodel on every keystroke, instead of the default behavior of updating the viewmodel property only when the textbox loses focus. You don't need UpdateSourceTrigger=PropertyChanged on the command binding or the Label.Content binding, because those properties cannot ever update the viewmodel property. They're OneWay by default, and by the nature of what they do.
Related
I'm learning MVVM pattern and I have a simple issue.
I have created a class whose properties I want to bind into text boxes inside a Dialog (in this case I only have one property):
public class NewObjectClass : ObservableObject
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
This is the ViewModel of the Dialog:
class ObjCreationViewModel : ObservableObject
{
// Class whose properties I want to bind in text boxes
private NewObjectClass _newObject;
public NewObjectClass NewObject
{
get { return _newObject; }
set { _newObject = value;
OnPropertyChanged("NewObject");
}
}
// String to test binding
private string _test;
public string Test
{
get { return _test; }
set { _test = value; }
}
// Create new object view
private DelegateCommand _createNewObject;
public DelegateCommand CreateNewObjectCmd => _createNewObject ?? (_createNewObject = new DelegateCommand(CreateNewObject));
void CreateNewObject()
{
// Do things in the future
}
public ObjCreationViewModel()
{
// Instance of New Object
NewObjectClass NewObject = new NewObjectClass();
// New object property that I want to show in TextBox
NewObject.Name = "Why this one is not working?";
// String text that works
Test = "Why this is working?";
}
}
And finally this is the view:
<UserControl x:Class="AOE.MVVM.View.ObjCreationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:AOE.MVVM.View"
xmlns:viewmodel="clr-namespace:AOE.MVVM.ViewModel"
mc:Ignorable="d"
MinHeight="500" MinWidth="500">
<UserControl.DataContext>
<viewmodel:ObjCreationViewModel/>
</UserControl.DataContext>
<Border Grid.ColumnSpan="4" CornerRadius="20"
Background="White" BorderBrush="Black" BorderThickness="1.5">
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1">
<StackPanel Orientation="Horizontal">
<Label Margin="10" Content="Object name:" Style="{StaticResource DescriptionLabelStyle}"/>
<TextBox x:Name="TextObjectName" Width="200" Height="25" Text="{Binding NewObject.Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</StackPanel>
</StackPanel>
<Button Grid.Row="1" Margin="5" Name="CreateBtn" Width="75" Height="30" Content="Create" FontFamily="Verdana" Foreground="#007BC0" FontWeight="Bold" Background="Transparent" BorderThickness="3" BorderBrush="#007BC0" VerticalAlignment="Bottom" Command="{Binding CreateNewObjectCmd}"/>
</Grid>
</Border>
The issue is that I can't bind the property Name of "NewObject" into the text box, however if I bind "Test", It works properly.
I want to show in the textbox the property value and also if I change the value in the textbox, change the property value of the instance "NewObject".
Anybody could tell me what is wrong?
PD "ObservableObject class that implements PropertyChange event":
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyname = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
}
The line
NewObjectClass NewObject = new NewObjectClass();
defines a local variable NewObject, but does not set the property NewObject.
Change it to
NewObject = new NewObjectClass();
I would like to get content from my combobox. I already tried some ways to do that, but It doesn't work correctly.
This is example of my combobox:
<ComboBox x:Name="cmbSomething" Grid.Column="1" Grid.Row="5" HorizontalAlignment="Center" Margin="0 100 0 0" PlaceholderText="NothingToShow">
<ComboBoxItem>First item</ComboBoxItem>
<ComboBoxItem>Second item</ComboBoxItem>
</ComboBox>
After I click the button, I want to display combobox selected item value.
string selectedcmb= cmbSomething.Items[cmbSomething.SelectedIndex].ToString();
await new Windows.UI.Popups.MessageDialog(selectedcmb, "Result").ShowAsync();
Why this code does not work?
My result instead of showing combobox content, it shows this text:
Windows.UI.Xaml.Controls.ComboBoxItem
You need the Content property of ComboBoxItem. So this should be what you want:
var comboBoxItem = cmbSomething.Items[cmbSomething.SelectedIndex] as ComboBoxItem;
if (comboBoxItem != null)
{
string selectedcmb = comboBoxItem.Content.ToString();
}
I have expanded on my suggestion regarding using models instead of direct UI code-behind access. These are the required parts:
BaseViewModel.cs
I use this in a lot of the view models in my work project. You could technically implement it directly in a view model, but I like it being centralized for re-use.
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Hashtable values = new Hashtable();
protected void SetValue(string name, object value)
{
this.values[name] = value;
OnPropertyChanged(name);
}
protected object GetValue(string name)
{
return this.values[name];
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
ComboViewModel.cs
This what you'll bind to make it easy to get values. I called it ComboViewModel because I'm only dealing with your ComboBox. You'll want a much bigger view model with a better name to handle all of your data binding.
public class ComboViewModel : BaseViewModel
{
public ComboViewModel()
{
Index = -1;
Value = string.Empty;
Items = null;
}
public int Index
{
get { return (int)GetValue("Index"); }
set { SetValue("Index", value); }
}
public string Value
{
get { return (string)GetValue("Value"); }
set { SetValue("Value", value); }
}
public List<string> Items
{
get { return (List<string>)GetValue("Items"); }
set { SetValue("Items",value); }
}
}
Window1.xaml
This is just something I made up to demonstrate/test it. Notice the various bindings.
<Window x:Class="SO37147147.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ComboBox x:Name="cmbSomething" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" HorizontalAlignment="Center" MinWidth="80"
ItemsSource="{Binding Path=Items}" SelectedIndex="{Binding Path=Index}" SelectedValue="{Binding Path=Value}"></ComboBox>
<TextBox x:Name="selectedItem" MinWidth="80" Grid.Row="2" Grid.Column="0" Text="{Binding Path=Value}" />
<Button x:Name="displaySelected" MinWidth="40" Grid.Row="2" Grid.Column="1" Content="Display" Click="displaySelected_Click" />
</Grid>
</Window>
Window1.xaml.cs
Here's the code-behind. Not much to it! Everything is accessed through the dataContext instance. There's no need to know control names, etc.
public partial class Window1 : Window
{
ComboViewModel dataContext = new ComboViewModel();
public Window1()
{
InitializeComponent();
dataContext.Items=new List<string>(new string[]{"First Item","Second Item"});
this.DataContext = dataContext;
}
private void displaySelected_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(String.Format("Selected item:\n\nIndex: {0}\nValue: {1}", dataContext.Index, dataContext.Value));
}
}
You can add business logic for populating models from a database, saving changes to a database, etc. When you alter the properties of the view model, the UI will automatically be updated.
I'm trying to learn Prism MVVM, and i'm making a window with 2 fields and a button, that gets enabled when this two fields aren't empty.
The problem is that i can't find a way to make the method ObservesProperty() work on an object (Pessoa in that case). The CanExecuteAtualizar() method only gets called at the app startup, and when i edit the textfields Nome or Sobrenome nothing happens to the button and the method isn't fired...
I tried to work without a model, putting the Nome, Sobrenome and UltimaAtualizacao properties directly in the ViewModel and it works fine, disabling the button according to the return of the method CanExecuteAtualizar, but i wanted to use it with a model instead. Is there a way to do this?
ViewAViewModel.cs
public class ViewAViewModel : BindableBase
{
private Pessoa _pessoa;
public Pessoa Pessoa
{
get { return _pessoa; }
set { SetProperty(ref _pessoa, value); }
}
public ICommand CommandAtualizar { get; set; }
public ViewAViewModel()
{
Pessoa = new Pessoa();
Pessoa.Nome = "Gabriel";
CommandAtualizar = new DelegateCommand(ExecuteAtualizar, CanExecuteAtualizar).ObservesProperty(() => Pessoa.Nome).ObservesProperty(() => Pessoa.Sobrenome);
}
public bool CanExecuteAtualizar()
{
return !string.IsNullOrWhiteSpace(Pessoa.Nome) && !string.IsNullOrWhiteSpace(Pessoa.Sobrenome);
}
public void ExecuteAtualizar()
{
Pessoa.UltimaAtualizacao = DateTime.Now;
}
}
Pessoa.cs
public class Pessoa : BindableBase
{
private string _nome;
public string Nome
{
get { return _nome; }
set { SetProperty(ref _nome, value); }
}
private string _sobrenome;
public string Sobrenome
{
get { return _sobrenome; }
set { SetProperty(ref _sobrenome, value); }
}
private DateTime? _ultimaAtualizacao;
public DateTime? UltimaAtualizacao
{
get { return _ultimaAtualizacao; }
set { SetProperty(ref _ultimaAtualizacao, value); }
}
}
ViewA.xaml
<UserControl x:Class="PrismDemo.Views.ViewA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PrismDemo.Views"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="500">
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Content="Nome:" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Left" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="0" Margin="3" TabIndex="0" Text="{Binding Pessoa.Nome, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="Sobrenome:" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="1" Margin="3" TabIndex="1" Text="{Binding Pessoa.Sobrenome, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="Última atualização:" Grid.Column="0" Grid.Row="2" HorizontalAlignment="Left" VerticalAlignment="Center" />
<Label Grid.Column="1" Grid.Row="2" Margin="3" HorizontalAlignment="Left" Content="{Binding Pessoa.UltimaAtualizacao, Mode=TwoWay}" />
<Button Content="Atualizar" Grid.Column="1" Grid.Row="3" Width="70" Margin="2,2,3,2" HorizontalAlignment="Right" Command="{Binding CommandAtualizar}" />
</Grid>
</UserControl>
DelegateCommand.ObservesPropery doesn't support complex object properties. It only supports properties that exist on the ViewModel in the command is defined. This is because the lifecycle of complex objects are unknown, and a memory leak would be created if many instances of the object was created. My recommendation would be to define you property like this:
private Pessoa _pessoa;
public Pessoa Pessoa
{
get { return _pessoa; }
set
{
if (_pessoa != null)
_pessoa.PropertyChanged -= PropertyChanged;
SetProperty(ref _pessoa, value);
if (_pessoa != null)
_pessoa.PropertyChanged += PropertyChanged;
}
}
Then in the PropertyChanged method, call DelegateCommand.RaiseCanExecuteChanged
EDIT:
Complex property support is now available in Prism for Xamarin.Forms 7.0.
It is true that DelegateCommand.ObservesPropery does not support complex objects, but its the way Commands are meant to be used with Prism. Manually calling PropertyChanged is an ugly hack in my opinion and should be avoided. Also it bloates the code again which Prism tries to reduce.
Moving all properties of the complex type into the ViewModel on the other hand would reduce the readability of the ViewModel. The very reason you create complex types in such scenarios is to avoid having too many single properties there in the first place.
But instead you could move the Command definition inside the complex type. Then you can set ObservesProperty for all simple properties in the constructor of the complex type and everything works as expected.
Model:
using Prism.Commands;
using Prism.Mvvm;
using static System.String;
public class LoginData : BindableBase
{
public LoginData()
{
DbAddr = DbName = DbUser = DbPw = "";
TestDbCommand = new DelegateCommand(TestDbConnection, CanTestDbConnection)
.ObservesProperty(() => DbAddr)
.ObservesProperty(() => DbName)
.ObservesProperty(() => DbUser)
.ObservesProperty(() => DbPw);
}
public DelegateCommand TestDbCommand { get; set; }
public bool CanTestDbConnection()
{
return !IsNullOrWhiteSpace(DbAddr)
&& !IsNullOrWhiteSpace(DbName)
&& !IsNullOrWhiteSpace(DbUser)
&& !IsNullOrWhiteSpace(DbPw);
}
public void TestDbConnection()
{
var t = new Thread(delegate () {
Status = DatabaseFunctions.TestDbConnection(this);
});
t.Start();
}
private string _dbAddr;
public string DbAddr
{
get => _dbAddr;
set => SetProperty(ref _dbAddr, value);
}
...
}
ViewModel:
public class DatabaseConfigurationViewModel
{
public DatabaseConfigurationViewModel()
{
CurrentLoginData = new LoginData(true);
}
public LoginData CurrentLoginData { get; set; }
}
View:
<UserControl x:Class="TestApp.Views.DatabaseConfiguration"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel Orientation="Vertical">
<Label>IP Adresse oder URL:</Label>
<TextBox Text="{Binding CurrentLoginData.DbAddr, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBox>
...
<Button Command="{Binding CurrentLoginData.TestDbCommand}">Teste Verbindung</Button>
</StackPanel>
</Grid>
In the Prism version that I have (7.0.0.362), you can use ObserveCanExecute, and pass property HasChanges that is updated on every property change of your entity.
TestDbCommand = new DelegateCommand(TestDbConnection).ObservesCanExecute(() => HasChanged);
Pessoa = new Pessoa();
Pessoa.PropertyChanged += Pessoa_PropertyChanged;
Then update HasChanges with a validation method in the constructor, and detach the method in the destructor.
private void Pessoa_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
HasChanged = ValidatePessoa(Pessoa);
}
~YourViewModel()
{
Pessoa.PropertyChanged -= Pessoa_PropertyChanged;
}
bool _hasChanged;
public bool HasChanged { get => _hasChanged; set => SetProperty(ref _hasChanged, value); }
I have a button on my page so that when I click it another small window pops up letting you enter a Country. This will then appear as a label on my main screen.
This is is the save button.
<Button x:Name="btnSave" Content="Save" HorizontalAlignment="Left" Margin="31,82,0,0" VerticalAlignment="Top" Width="75" Click="btnSave_Click"/>
Then I have the code behind.
ICommand _AddCountry;
ViewModel _viewmodel;
public NewCountry(ICommand AddCountry, ViewModel viewModel)
{
InitializeComponent();
_Addcountry = AddCountry;
_viewmodel = viewModel;
}
private void btnSave_Click(object sender, RoutedEventArgs e)
{
string countryName = txtCountry.Text;
_viewmodel.RenameCountry(countryName);
_Addcountry.Execute(null);
this.Close();
}
The label is binded to the Title so that the Country will appear as that label.
<Label Content="{Binding Country}" Width="500" HorizontalAlignment="Left" >
Now this is my problem, when I create that Country at runtime, it creates a new instance of the NewCountryClass which has a <String>Country Property inside BUT also has a ICollection<string> Property named Places.
public class NewCountryClass : INotifyPropertyChanged
private string _country;
public string Country
{
get { return _country; }
set
{
if (_country.Equals(value))
return;
_country = value;
RaisePropertyChanged(() => Country);
}
}
private ICollection<string> _places;
public ICollection<string> Places
{
get
{
if (_places == null)
_places = new ObservableCollection<string>();
return _places;
}
set
{
if (value == _places)
return;
_places = value;
RaisePropertyChanged(() => Places);
}
}
When I right click on that Countrys label on my main screen and click Add Place on the drop down, I want that place to get added to that Country Instance of that class, inside the ICollection<string>Places.
The problem comes when if I create 3 titles on the screen(so it creates 3 different instances of AddCountryClass) that when I click Add place I cant find the instance of the Country I clicked on.
Other useful code in my ViewModel:
public void AddCountryCollection()
{
NewCountryClass newCountryClass = new NewCountyClass(newCountry,"");
Collections.Add(newCountryClass);
}
This is where I create the instance of the Country, When I enter my Country
public ObservableCollection<NewCountryClass> Collections
{
get { return collections; }
set { collections = value; OnPropertyChanged(() => Collections); }
}
This is where the Country gets passed**
public void AddPlaces()
{
NewCountryClass newCountryClass;
string Item = SelectedCountry.Country;
Collections.Select(w => w.Country);
}
This was me trying to have a go but It didn't achieve anything
Summary
If I create 3 Countrys which use 3 different instances of class NewContryClass, When I want to add something into the ICollection<string>Places of one of the Countrys, how do I find it?
disclaim i know non view stuff belongs in to the VM but thats the way i do it and i'm fine with it
MainV
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!--Here is how you could select your Country-->
<Label Content="Country" Grid.Column="0" Grid.Row="0" />
<ComboBox Grid.Column="1" Grid.Row="0"
DisplayMemberPath="Country" Margin="0,0,30,5"
ItemsSource="{Binding CountryCollection}"
SelectedItem="{Binding SelectedCountryItem,UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="+" Grid.Column="1" Grid.Row="0" Height="24" HorizontalAlignment="Right" Width="24" Margin="0,0,0,5"
Command="{Binding NewCountryCommand}"/>
<!--Here is how you could show your selected Country-->
<Label Content="Selected" Grid.Column="0" Grid.Row="1" />
<Grid Grid.Column="1" Grid.Row="1" DataContext="{Binding SelectedCountryItem}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Content="{Binding Country}"
Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2"/>
</Grid>
</Grid>
MainVM
private RelayCommand _NewCountryCommand; // you can find the RelayCommand Class on the web
public ICommand NewCountryCommand
{
get { return _NewCountryCommand ?? (_NewCountryCommand= new RelayCommand(param => this.OnNewCountry())); }
}
public void OnNewCountry()
{
var window = new Window(); // or whatever View you use
var vm = new NewCountryClass();
window.DataContext= vm;
window.ShowDialog();
if(vm.Result)// Result will be set to true
// if the user Click the Save Button in your CountryV
{
// here will be your logic if the user want to save his stuff
// in my case
CountryCollection.Add(vm)
SelectedCountryItem = vm;
}
}
public ObservableCollection<NewCountryClass> CountryCollection
{
get { return collections; }
set { collections = value;
OnPropertyChanged(() => CountryCollection); }
}
public NewCountryClass SelectedCountryItem
{
get { return selectedCountryItem; //}
set { selectedCountryItem = value;
OnPropertyChanged(() => SelectedCountryItem); }
}
and thats it.
You can also take a look at this Tut which provided some Samplecode
This is my first post on StackOverflow and also my first Question.
I had created a UserControl in WPF with the MVVM Pattern (called Block).
This UserControl should be used in another UserControl, as DataTemplate of a ListBox (called Sector).
The Block Control should be used as standalone and as DataTemplate for different UserControls.
If I set the DataContext in the View of Block, the standalone Version works great, but not as part of the Sector View.
If I don´t set the DataContext in the View of Block, it works in Sector but don’t standalone.
My question is, is it the only way to leave the DataContext in the View of Block and set it in the View I used the Control or the ViewModel?
Here is my Code:
Model of Block:
public class BlockModel : ModelBase
{
#region private Variables
string blockName = "Block";
string blockContent = "00000";
#endregion private Variables
#region Properties
public string BlockName
{
get { return blockName; }
set { blockName = value; NotifyPropertyChange("BlockName "); }
}
public string BlockContent
{
get { return blockContent; }
set { blockContent = value; NotifyPropertyChange("BlockContent"); }
}
#endregion Properties
#region ctor
public BlockModel () { }
#endregion ctor
}
ViewModel of Block:
public class BlockViewModel : ViewModelBase
{
#region private Variables
string charToFill = "0";
DelegateCommand fillWithChar;
BlockModel dataModel = new BlockModel();
#endregion private Variables
#region Properties
public Models. BlockModel DataModel
{
get { return dataModel; }
set { dataModel = value; }
}
public string BlockContent
{
get { return dataModel. BlockContent; }
set
{
if (dataModel. BlockContent != value)
{
dataModel. BlockContent = value;
NotifyPropertyChange ("BlockContent");
}
}
}
public string BlockName
{
get { return dataModel. BlockName; }
set
{
if (dataModel. BlockName != value)
{
dataModel. BlockName = value;
NotifyPropertyChange("BlockName");
}
}
}
public string CharToFill
{
get { return charToFill; }
set
{
if (charToFill != value)
{
charToFill = value;
NotifyPropertyChange ("CharToFill");
}
}
}
public ICommand FillWithChar
{
get
{
if (fillWithChar == null)
fillWithChar = new DelegateCommand(FillText);
return fillWithChar;
}
}
#endregion Properties
#region ctor
public BlockViewModel()
{
}
#endregion ctor
#region Methods
private void FillText()
{
CodingBlockContent = CodingBlockContent.PadLeft(32, charToFill[0]);
}
#endregion Methods
}
View of Block:
<UserControl x:Class="…./Block"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:…/Controls"
mc:Ignorable="d" d:DesignWidth="460" d:DesignHeight="44">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="124" />
<ColumnDefinition Width="301" />
<ColumnDefinition Width="30"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding BlockName}"
HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="14" Margin="2,6"/>
<ComboBox FontFamily="Consolas" Grid.Row="00" Grid.Column="1"
Text="{Binding BlockContent, Mode=TwoWay}"
Tag="{Binding BlockName}" Name="cbxBlock" FontSize="14"
HorizontalAlignment="Left" VerticalAlignment="Center" Width="290" Margin="1,4,0,5" IsEditable="True" Height="26">
<ComboBoxItem Content=""></ComboBoxItem>
<ComboBoxItem Content="0000000000000"></ComboBoxItem>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Horizontal">
<TextBox Margin="10,2,0,2" Text="0" Width="24" FontSize="14" Name="tbxChar" MaxLength="1"></TextBox>
<TextBlock Margin="10,2,0,2" Text="auffüllen" VerticalAlignment="Center" FontSize="14"></TextBlock>
<Button Margin="10,2,0,2" Content="Ausführen" Width="100" Command="{Binding FillWithChar}"></Button>
</StackPanel>
</ComboBox>
<TextBlock Grid.Row="0" Width="30" Grid.Column="2" Text="{Binding CodingBlockContent.Length}"
HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="14" Margin="2,6,0,6" Grid.ColumnSpan="2"/>
</Grid>
Model of Sector:
public class SectorModel
{
#region private Variables
int blockAmount = 4;
string sectorNumber = "Sektor";
int blockBegin = 0;
bool isSectorInUse = false;
#endregion private Variables
#region Properties
public string SectorNumber
{
get { return sectorNumber; }
set { sectorNumber = value;}
}
public int BlockBegin
{
get { return blockBegin; }
set
{
blockBegin = value;
}
}
public bool IsSectorInUse
{
get { return isSectorInUse; }
set { isSectorInUse = value;}
}
public int BlockAmount
{
get { return blockAmount; }
set
{
blockAmount = value;;
}
}
#endregion Properties
public SectorModel()
{
}
}
ViewModel of Sector:
public class SectorViewModel : ViewModelBase
{
#region private Variables
SectorModel dataModel = new SectorModel();
ObservableCollection<BlockViewModel> sectorBlocks = new ObservableCollection<BlockViewModel>();
#endregion private Variables
#region Properties
public SectorModel DataModel
{
get { return dataModel; }
set { dataModel = value; }
}
public ObservableCollection<BlockViewModel> SectorBlocks
{
get { return sectorBlocks; }
set { sectorBlocks = value; NotifyPropertyChange ("SectorBlocks"); }
}
public string SectorNumber
{
get { return "Sektor " + DataModel.SectorNumber; }
set { DataModel.SectorNumber = value; NotifyPropertyChange ("SectorNumber"); }
}
public int BlockBegin
{
get { return DataModel.BlockBegin; }
set
{
DataModel.BlockBegin = value;
SetBlocks();
OnPropertyChanged("BlockBegin");
}
}
public bool IsSectorInUse
{
get { return DataModel.IsSectorInUse; }
set { DataModel.IsSectorInUse = value; NotifyPropertyChange ("IsSectorInUse"); }
}
public int BlockAmount
{
get { return DataModel.BlockAmount; }
set
{
DataModel.BlockAmount = value;
SetBlocks();
NotifyPropertyChange ("CodingBlockAmount");
}
}
#endregion Properties
void SetBlocks()
{
while (SectorBlocks.Count != BlockAmount)
{
SectorBlocks.Add(new BlockViewModel());
}
int begin = BlockBegin;
foreach (BlockViewModel block in SectorBlocks)
{
block.CodingBlockName = "Block " + begin.ToString().PadLeft(2, '0');
block++;
}
}
public SectorViewModel()
{
SetBlocks();
}
}
View of Sector:
<UserControl xmlns:Views="clr-namespace:…/RFIDControls" x:Class="…/Sector"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="473">
<Border BorderBrush="Black" BorderThickness="0,0,0,1">
<Expander Name="expMain" Margin="0,0,4,0">
<Expander.Header>
<Grid Name="grdHeader">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Height="24" Text="{Binding SectorNumber}" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="14" FontWeight="Bold" Panel.ZIndex="98"/>
<CheckBox Grid.Column="2" VerticalAlignment="Center" IsChecked="{Binding IsSectorInUse}"></CheckBox>
</Grid>
</Expander.Header>
<Grid>
<ListBox ItemsSource="{Binding SectorBlocks}" Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type Views:BlockViewModel}">
<Views:Block/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Expander>
</Border>
Thank you.
With kind regards from Germany
Dominik
PS: I hope my English is not so bad as I suppose
This is a common problem for new users of WPF. You have two possible solutions. When using MVVM, you shouldn't need to set the DataContext of the UserControl anywhere. Instead, you can simply add a DataTemplate to your App.xaml to make the pairing:
<DataTemplate DataType="{x:Type ViewModels:BlockViewModel}">
<Views:BlockView />
</DataTemplate>
In this way, whenever you show an instance of your BlockViewModel, the Framework will display the related BlockView instead (like you have done in your SectorView class).
The alternative option is to not use MVVM for the UserControl (use Bindable DependencyPropertys instead), to not set its DataContext internally and to use RelativeSource Bindings on the elements inside instead:
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding DataContext.BlockName,
RelativeSource={RelativeSource AncestorType={
x:Type YourXmlNamespacePrefix:BlockView}}}" HorizontalAlignment="Left"
VerticalAlignment="Center" FontSize="14" Margin="2,6"/>
Using this method, the RelativeSource Binding will look at whatever object is currently set as the DataContext.
Sounds like Block act as an control, which seems participate in other usercontrol or part of Template, in my opinion, mvvm doesn't work in here, you should use Dependency property instead.
Forgive my poor english.