Please allow me to present a simplified version of my problem:
Lets say I have a main window called MainWindow in which I would like to display some Person objects in MainWindow. Now, in order to instantiate these Person objects I need a bunch of different fields such as name, age, profession, favourite food, etc...
Here is my solution:
I try to get all input fields and instantiate a Person in a secondary window and then send back the result to the main form.
MainWindow has a public method as follows:
public void (Person input)
{
// use the fields in input to add details to window
}
I have another window in the project called PersonInput that takes in its constructor a reference to a MainWindow and saves it in a field.
private MainWindow owner;
public PersonInput(MainWindow parent)
{
InitializeComponent();
owner = parent;
}
PersonInput has a number of input fields corresponding to the required fields of a Person object.
in addition it has a button called "AddPerson" with an associated onClick event handler as follows: (pseudoCode)
private void button_Click(object sender, RoutedEventArgs e)
{
//get all fields from this form..
String enteredName = this.nameText.Text;
//get more fields....
Person p = new Person(...);
//owner is MainWindow, send Back the Person so details can be displayed
owner.addPerson(p);
this.Close();
}
as you would expect, MainWindow has a Button named "AddPersonButton" which has an on click event handler like this:
private void button_Click(object sender, RoutedEventArgs e)
{
PersonInput x = new PersonInput(this); //pass this as a reference
//so this window can send us back the result when they have it
x.Show(); //open the child window so user can enter information
}
While this method works, I am quite convinced it is not the best practice way to do it. I would like to learn the idiomatic .net WPF way of doing this. Please enlighten me
PersonInput.xaml.cs
public class PersonInput : Window
{
public void PersonInput()
{
InitializeComponent();
Owner = Application.Current.MainWindow;
}
public static Person ShowDialog(Person initializer)
{
var vm = new PersonViewModel(initializer);
var dlg = new PersonInput() { DataContext = vm };
if (dlg.ShowDialog().GetValueOrDefault(false))
{
return vm.ToPerson();
}
return null;
}
private void OK_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
}
}
PersonInputViewModel.cs
public class PersonViewModel : ViewModelBase
{
public PersonViewModel(Person person = null)
{
if (person != null)
{
// Assuming Person has FirstName and LastName properties
FirstName = person.FirstName;
LastName = person.LastName;
// etc. etc. for all the rest
}
}
public Person ToPerson()
{
return new Person()
{
FirstName = this.FirstName,
LastName = this.LastName,
// etc. etc. for all other properties
};
}
private string _firstName = null;
public string FirstName {
get { return _firstName; }
set {
if (value != _firstName) {
_firstName = value;
OnPropertyChanged(nameof(FirstName));
}
}
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
PersonInput.xaml
<Window xmlns:blahblahblah="Blah blah blah" etc etc
Title="Person"
Height="640"
Width="480"
ShowInTaskbar="False"
ResizeMode="CanResizeWithGrip"
WindowStartupLocation="CenterOwner"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="180" Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0">First Name</Label>
<TextBox
Grid.Row="0"
Grid.Column="1"
Text="{Binding FirstName}"
/>
<Label Grid.Row="1" Grid.Column="0">Last Name</Label>
<TextBox
Grid.Row="1"
Grid.Column="1"
Text="{Binding LastName}"
/>
<StackPanel
Orientation="Horizontal"
Grid.Column="1"
Grid.Row="10"
HorizontalAlignment="Right"
>
<Button Content="_OK" Click="OK_Click" />
<Button Content="_Cancel" Click="Cancel_Click" />
</StackPanel>
</Grid>
</Window>
The biggest weakness of this approach is the strong coupling between MainWindow and PersonInput.
A slightly better approach would be to use Observer Pattern and have the Main Window anonymously subscribe.
Simple solution example code:
public interface IAddPersonObserver
{
void OnPersonAdded(Person person);
}
public interface IAddPersonObservable
{
void Subscribe(IAddPersonObserver observer);
void Unsubscribe(IAddPersonObserver observer);
}
public class MainWindow : IAddPersonObserver
{
...
private void button_Click(object sender, RoutedEventArgs e)
{
PersonInput x = new PersonInput();
x.Subscribe(this);
x.Show();
}
public void OnPersonAdded(Person addedPerson)
{
addPerson(addedPerson); // or whatever view update code you want
}
}
Further improvements would revolve around separating the MainWindow from knowing what view gets created or shown, and having an intermediary object (such as a PersonRepository) store/hold/provide the important business data. This is much better than having the application data actually live inside the Views and Application Windows.
Another use MVVM practice:
Solution structure
public class Person {
public int Id { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
public class PersonListViewModel : DependencyObject {
public ObservableCollection<Person> Items { get; set; }
public Person CurrentPerson
{
get { return (Person)GetValue(CurrentPersonProperty); }
set { SetValue(CurrentPersonProperty, value); }
}
public static readonly DependencyProperty CurrentPersonProperty = DependencyProperty.Register("CurrentPerson", typeof(Person), typeof(PersonListViewModel));
public ICommand AddCommand { get; set; }
public ICommand EditCommand { get; set; }
public PersonListViewModel() {
Items = new ObservableCollection<Person>();
AddCommand = new RelayCommand(p=> add() );
EditCommand = new RelayCommand(p=> { return CurrentPerson != null; }, p => edit());
}
private void add() {
Person p= new Person();
p.Id = Items.Count();
p.Name = "New Name";
p.Birthday = DateTime.Now;
Items.Add(p);
}
private void edit() {
var viewModel = new PersonItemViewModel(CurrentPerson);
var view = new View.PersonEditWindow();
view.DataContext = viewModel;
view.Show();
}
}
public class PersonItemViewModel : DependencyObject {
Person person;
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(PersonItemViewModel) );
public DateTime Birthday
{
get { return (DateTime)GetValue(BirthdayProperty); }
set { SetValue(BirthdayProperty, value); }
}
public static readonly DependencyProperty BirthdayProperty = DependencyProperty.Register("Birthday", typeof(DateTime), typeof(PersonItemViewModel));
public PersonItemViewModel() {
}
public PersonItemViewModel(Person source) {
this.person = source;
Name = person.Name;
Birthday = person.Birthday;
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) {
base.OnPropertyChanged(e);
if (e.Property == NameProperty) {
person.Name = (string) e.NewValue;
}
if (e.Property == BirthdayProperty) {
person.Birthday = (DateTime)e.NewValue;
}
}
}
List Form:
<Window x:Class="WpfApplication1.View.PersonListWindow"
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:local="clr-namespace:WpfApplication1.View"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
Title="PersonListWindow" Height="300" Width="300"
xmlns:viewModel="clr-namespace:WpfApplication1.ViewModel"
d:DataContext="{d:DesignInstance Type=viewModel:PersonListViewModel, IsDesignTimeCreatable=True}"
>
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding Items}" SelectedItem="{Binding CurrentPerson, Mode=TwoWay}">
</DataGrid>
<StackPanel Grid.Row="1" Height="32" Orientation="Horizontal" HorizontalAlignment="Center">
<Button Margin="4" Command="{Binding AddCommand}">Add</Button>
<Button Margin="4" Command="{Binding EditCommand}">Edit</Button>
</StackPanel>
</Grid>
Edit Form:
<Window x:Class="WpfApplication1.View.PersonEditWindow"
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:local="clr-namespace:WpfApplication1.View"
mc:Ignorable="d"
Title="PersonEditWindow" Height="300" Width="300"
xmlns:viewModel="clr-namespace:WpfApplication1.ViewModel"
d:DataContext="{d:DesignInstance Type=viewModel:PersonItemViewModel, IsDesignTimeCreatable=True}"
>
<Grid Margin="20">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0">Name</TextBlock>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Name}"></TextBox>
<TextBlock Grid.Row="1">Birthday</TextBlock>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Birthday}"></TextBox>
</Grid>
Results (Image)
Related
Problem
I want to refresh my wpf view when a change is made in a List of objects in my application, but it wont register the INotifyChanged method when I change a value.
What I've tried
I went to multiple different stackoverflow pages with sort of the same problem but I don't get it working right. It wont register a change in a object in the list.
my code
below is the code for the MainWindow of the WPF application in wher with the last button click I change the value of XLocation in an object out of a list.
public partial class MainWindow : Window
{
private string filePathArtist { get; set; }
private string filePathGrid { get; set; }
public Game.Game Game { get; set; }
public MainWindow()
{
InitializeComponent();
filePathGrid = String.Empty;
filePathArtist = String.Empty;
}
private void BtnOpen_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
bool? res = openFileDialog.ShowDialog();
if (res == true)
{
string filepathgrid = openFileDialog.FileName;
filePathGrid = filepathgrid;
GridTextBox.Text = filepathgrid;
}
}
private void PickArtistBtn_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
bool? res = openFileDialog.ShowDialog();
if (res == true)
{
string filepathartist = openFileDialog.FileName;
filePathArtist = filepathartist;
ArtistTextBox.Text = filepathartist;
}
}
private void CreateGridBtn_Click(object sender, RoutedEventArgs e)
{
Game = new Game.Game(filePathGrid, filePathArtist);
this.DataContext = Game;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Game.Artists[0].XLocation = 30;
}
}
Next code is the Game class where is implemented a INotfyPropertyChanged on the list of Artists.
public class Game : INotifyPropertyChanged
{
public List<Artist> _artists;
public List<Artist> Artists
{
get
{
return _artists;
}
set
{
_artists = value;
OnPropertyChanged("Artists");
}
}
public List<ITile> Tiles { get; set; }
public Game()
{
}
public Game(string graphPath, string artistPath)
{
IDataParser graphParser = DataFactory.DataFactory.Instance.CreateParser(graphPath);
IDataParser artistParser = DataFactory.DataFactory.Instance.CreateParser(artistPath);
Tiles = graphParser.ParseGridData(graphPath);
Artists = artistParser.ParseArtistData(artistPath);
Test = "new Game";
}
public string Test { get; set; } = "t";
public event PropertyChangedEventHandler? PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Ive also added the INotifyPropertyChanged in the Artists class
public class Artist : INotifyPropertyChanged
{
private float _xLocation;
private float _yLocation;
private int _xVelocity;
private int _yVelocity;
public float XLocation
{
get => _xLocation;
set
{
_xLocation = value;
OnPropertyChanged("XLocation");
}
}
public float ConvertedXLoc
{
get => XLocation * (float)3.75;
set { }
}
public float YLocation
{
get => _yLocation;
set
{
_yLocation = value;
OnPropertyChanged("YLocation");
}
}
public float ConvertedYLoc
{
get => YLocation * (float)3.75;
set { }
}
public int XVelocity
{
get => _xVelocity;
set
{
_xVelocity = value;
}
}
public int YVelocity
{
get => _yVelocity;
set
{
_yVelocity = value;
}
}
public event PropertyChangedEventHandler? PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then here is the Xaml code where I bind the objects to the wpf UI.
<Window x:Class="BroadwayBoogie.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:game="clr-namespace:BroadwayBoogie.Game"
mc:Ignorable="d"
Title="MainWindow" Height="900" Width="900"
>
<Window.DataContext>
<game:Game/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="201*"/>
<ColumnDefinition Width="199*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="21*"/>
<RowDefinition Height="401*"/>
<RowDefinition Height="20*"/>
</Grid.RowDefinitions>
<Button x:Name="BtnOpen" Content="Pick Grid" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Click="BtnOpen_Click"/>
<TextBox x:Name="GridTextBox" HorizontalAlignment="Center" TextWrapping="NoWrap" VerticalAlignment="Center" Width="266" />
<Button x:Name="PickArtistBtn" Content="Pick Artist" HorizontalAlignment="Left" Margin="356,0,0,0" VerticalAlignment="Center" Click="PickArtistBtn_Click" RenderTransformOrigin="-0.135,0.647"/>
<TextBox x:Name="ArtistTextBox" HorizontalAlignment="Left" Margin="30,14,0,0" TextWrapping="NoWrap" VerticalAlignment="Top" Width="231" Grid.Column="1"/>
<Button x:Name="CreateGridBtn" Grid.Column="1" Content="Create Grid" HorizontalAlignment="Left" Margin="311,14,0,0" VerticalAlignment="Top" Click="CreateGridBtn_Click"/>
<Canvas Width="800" Height="800" Grid.ColumnSpan="2" Margin="49,15,51,27" Grid.Row="1" Background="DarkSeaGreen" Grid.RowSpan="2">
<ItemsControl Name="tilesItemsControl" ItemsSource="{Binding Tiles}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<Rectangle
Width="15"
Height="15"
Fill="{Binding Color}"
Canvas.Left ="{Binding ConvertedXLoc}"
Canvas.Top="{Binding ConvertedYLoc}" />
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl Name="ArtistItemsControl" ItemsSource="{Binding Artists}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<Rectangle
Width="3.75"
Height="3.75"
Fill="Black"
Canvas.Left ="{Binding ConvertedXLoc}"
Canvas.Top="{Binding ConvertedYLoc}" />
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
<Grid/>
<Button Content="Button" HorizontalAlignment="Left" Margin="4,0,0,0" Grid.Row="2" VerticalAlignment="Center" Click="Button_Click"/>
</Grid>
So with the press of the button I added for testing purposes. It changes a value in the List and then the PropertyChanged method should detect that but it doesn't detect it it just skips it.
Question
So my basic question is, how do I detect the change of a property from objects out of the List of Artists.
OnPropertyChanged will only be executed when the property itself is changed. A new item in a list is not a property change. That's the reason why no updates happens.
Instead a List, try an ObservableCollection. An ObservableCollection implements an additional INotifyCollectionChanged which makes the UI able to react on changing items in the list.
I have a UserControl called "UserControllerIo" and this is what it has:
public ObservableCollection<string> Messages { get; set; }
public UserControllerIo()
{
Messages = new ObservableCollection<string>();
InitializeComponent();
IoComponentViewModel.Instance = new IoComponentViewModel();
Label1.DataContext = IoComponentViewModel.Instance;
Messages.Add(Label1.Text);
}
I consume this in my xml like so:
<Grid>
<Label>
<TextBlock x:Name="Label1" TextWrapping="WrapWithOverflow"
Text="{Binding Path=XState, Mode=OneWay}">
</TextBlock>
</Label>
<ListView
x:Name="ListView"
ItemsSource="{Binding Messages}" />
</Grid>
I have a view model for this control:
class IoComponentViewModel : INotifyPropertyChanged
{
public static IoComponentViewModel Instance { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private string _xState;
public string XState
{
get { return _xState; }
set
{
_xState = value;
OnPropertyChanged($"XState");
}
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And I invoke to populate the list on another class like so:
case x:
IoComponentViewModel.Instance.XState = msg;
break;
My problem is, it is not showing in my Listview although I can see it in my label. Can you please show me how. Thank you.
I don't know how much I understood your task from the provided code, but look at this implementation variant.
IoComponentViewModel:
public class IoComponentViewModel : INotifyPropertyChanged
{
public static IoComponentViewModel Instance { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private string _xState;
public string XState
{
get { return _xState; }
set
{
if (_xState == value)
return;
XStates.Add(_xState = value);
OnPropertyChanged($"XState");
}
}
public ObservableCollection<string> XStates { get; } = new ObservableCollection<string>();
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<Grid x:Name="PART_Grid">
<Grid.DataContext>
<local:IoComponentViewModel/>
</Grid.DataContext>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<!--<Label>-->
<TextBlock x:Name="Label1" TextWrapping="WrapWithOverflow"
Text="{Binding XState, Mode=OneWay}">
</TextBlock>
<!--</Label>-->
<ListView Grid.Row="1"
x:Name="ListView"
ItemsSource="{Binding XStates}" />
</Grid>
Code Behind:
//public ObservableCollection<string> Messages { get; set; }
public UserControllerIo()
{
//Messages = new ObservableCollection<string>();
InitializeComponent();
// IoComponentViewModel.Instance = new IoComponentViewModel();
//Label1.DataContext = IoComponentViewModel.Instance;
IoComponentViewModel.Instance = (IoComponentViewModel)PART_Grid.DataContext;
//Messages.Add(Label1.Text);
}
I misread the question initially. There are two problems. Your list is not binding to the view model, so you need an element reference.
<UserControl x:Class="StackOverflow.UserControllerIo"
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:StackOverflow"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
x:Name="MyUserControl"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label>
<TextBlock Foreground="Black" x:Name="Label1" TextWrapping="WrapWithOverflow"
Text="{Binding Path=XState, Mode=OneWay}">
</TextBlock>
</Label>
<ListView Grid.Row="1"
x:Name="ListView"
ItemsSource="{Binding Messages, ElementName=MyUserControl}" >
</ListView>
</Grid>
Secondly, at the point where you add Label1.Text to your data binding is not ready. So you will need to wait for binding before you read the text, for example in load event like this:
public partial class UserControllerIo : UserControl
{
public ObservableCollection<string> Messages { get; set; }
public UserControllerIo()
{
Messages = new ObservableCollection<string>();
InitializeComponent();
IoComponentViewModel.Instance = new IoComponentViewModel();
Label1.DataContext = IoComponentViewModel.Instance;
IoComponentViewModel.Instance.XState = "Something";
Loaded += UserControllerIo_Loaded;
}
private void UserControllerIo_Loaded(object sender, RoutedEventArgs e)
{
Messages.Add(Label1.Text);
}
}
EDIT:
my first tests mislead me, by testing with an int property for adding values to the List on runtime.
ObservableCollection updates anyway!
The problem is how you declared the Messages Property. If you have a Property on a Control it needs to be a dependency Property to notify the UI.
replace
public ObservableCollection<string> Messages { get; set; }
with
public ObservableCollection<string> Messages
{
get { return (ObservableCollection<string>)GetValue(MessagesProperty); }
set { SetValue(MessagesProperty, value); }
}
public static readonly DependencyProperty MessagesProperty =
DependencyProperty.Register("Messages", typeof(ObservableCollection<string>), typeof(UserControllerIo), new PropertyMetadata(null));
and you should be fine.
OR
you could imlpement INotifyPropertyChanged on your UserControl class.
And don't forget to maintain #Clemens' comment about binding!!!
ItemsSource="{Binding Messages, RelativeSource={RelativeSource AncestorType=UserControl}}
I have to navigate to another tab by a button click from the first tab in a WPF MVVM Application(c#).
I am trying to achieve this by adding binding to Selected Index property in tab control.There are two different view models are used in the first tab.After adding binding to Selected Index property in tab control it loses the rest of view model's access and No data is present on the text boxes in the first tab. Also navigation is not working . how can I use tab navigation if the window has multiple view models. please see sample code.
XAML file
MainWindow.xaml
<Grid>
<TabControl SelectedIndex="{Binding SelectedTab,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
DataContext="{Binding processVM}">
<TabItem Header="Home">
<Grid ShowGridLines="false" >
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<TextBox Name="txtCustomerName"
Grid.Row="0" Grid.Column="1"
Text="{Binding CustomerName}"
DataContext="{Binding customerVM}"></TextBox>
<TextBox Name="txtDepositAmount"
Grid.Row="1" Grid.Column="1"
Text="{Binding DepositAmount}"
DataContext="{Binding customerVM}"></TextBox>
<Button Content="Click" Width="100" Height="50"
Grid.Row="2"
DataContext="{Binding processVM}"
Command="{Binding ButtonCommand}"
/>
</Grid>
Code behind
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel()
{
processVM = new ProcessViewModel(),
customerVM = new CustomerViewModel()
};
}
}
View Models
MainWindowViewModel.cs
class MainWindowViewModel
{
public ProcessViewModel processVM { get; set; }
public CustomerViewModel customerVM { get; set; }
}
ProcessViewModel.cs
public class ProcessViewModel: INotifyPropertyChanged
{
private string depositAmount;
public string DepositAmount
{
get { return depositAmount; }
set {
depositAmount = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("DepositAmount"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private ICommand m_ButtonCommand;
public ICommand ButtonCommand
{
get
{
return m_ButtonCommand;
}
set
{
m_ButtonCommand = value;
}
}
private int selectedTab;
public int SelectedTab
{
get { return selectedTab; }
set
{
selectedTab = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SelectedTab"));
}
}
public ProcessViewModel()
{
ButtonCommand = new RelayCommand(new Action<object>(clickbutton));
depositAmount = "450";
}
public void clickbutton(object obj)
{
MessageBox.Show("clicked");
SelectedTab = 1;
}
}
CustomerViewModel.cs
class CustomerViewModel: ProcessViewModel, INotifyPropertyChanged
{
private string customerName;
public string CustomerName
{
get { return customerName; }
set { customerName = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("CustomerName"));
}
}
public CustomerViewModel()
{
CustomerName = "Alex";
}
public event PropertyChangedEventHandler PropertyChanged;
}
Before adding binding for selected index there were no issues.
Your problem is that you're setting TabControl.DataContext. DataContext is inherited, so all the controls inside it are now using processVM as their binding source instead of MainWindowViewModel.
Instead of setting TabControl.DataContext, change the SelectedIndex binding to this:
SelectedIndex="{Binding processVM.SelectedTab, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
I have been reading my code over and over for the past hour and cannot seem to understand why the changes I have added now don't allow my page to display (the page not displaying is NameEntryPage). The app starts on a the StartPage which has two buttons allowing the user to choose either One Player Game or Two Player Game. If the user chooses Two Player Game, then it navigates to NameEntryPage where the user can enter names for the two players. NameEntryPage has a button that when clicked passes the names entered and navigates to TwoPlayerPage. I have checked StartPage, OnePlayerPage, and TwoPlayerPage and they all still work. However, NameEntryPage does not load anything (and the button on StartPage that takes you there does not work either. To check the other pages I had to change the code in App() to begin on those pages)
App() code
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new Views.NameEntryPage());
}
NameEntryPage.xaml code
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="SampleApp.Views.NameEntryPage">
<ContentPage.Content>
<StackLayout Margin="20">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
<Entry
x:Name="EntryNameP1"
Placeholder="Enter a name for Player 1"
Text="{Binding NameP1}"
Grid.Row="0" Grid.Column="0"/>
<Entry x:Name="EntryNameP2"
Placeholder="Enter a name for Player 2"
Text="{Binding NameP2}"
Grid.Row="1" Grid.Column="0"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Text="Start"/>
</Grid>
</StackLayout>
</ContentPage.Content>
code behind
namespace SampleApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class NameEntryPage : ContentPage
{
public NameEntryPage()
{
NavigationPage.SetHasNavigationBar(this, false);
InitializeComponent();
BindingContext = new NameEntryPageViewModel(Navigation, EntryNameP1.Text, EntryNameP2.Text);
}
}
}
VM code
public class NameEntryPageViewModel : INotifyPropertyChanged
{
public INavigation Navigation { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string nameP1;
public string NameP1
{
get
{
return NameP1;
}
set
{
if (NameP1 != value)
{
nameP1 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("NameP1"));
}
}
}
private string nameP2;
public string NameP2
{
get
{
return NameP2;
}
set
{
if (NameP2 != value)
{
nameP2 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("NameP1"));
}
}
}
public Command StartGameCommand { get; set; }
public NameEntryPageViewModel(INavigation navigation, string name1, string name2)
{
nameP1 = name1;
nameP2 = name2;
this.Navigation = navigation;
StartGameCommand = new Command(() => StartGame(nameP1, nameP2));
}
public NameEntryPageViewModel(INavigation navigation)
{
nameP1 = "Player 1";
nameP2 = "Player 2";
this.Navigation = navigation;
StartGameCommand = new Command(() => StartGame(nameP1, nameP2));
}
private void StartGame(string name1, string name2)
{
this.Navigation.PushAsync(new Views.TwoPlayerPage(name1, name2));
}
}
this will create an infinite loop
public string NameP1
{
get
{
return NameP1;
}
NameP1 will call the getter for NameP1, which calls the getter for NameP1, recursively until you crash
instead your getter should return the private nameP1 variable
public string NameP1
{
get
{
return namep1;
}
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.