Textbox not updating when static object method to update property is called - c#

First, let me state I am new to C# altogether. At the moment I am working on a program, and have been trying to get something that I thought would be rather simple to do. This is apparently not the case. I'll show here a very small example of what I have been trying to do. This is functionally identical, but without all the extra things that I have in the real project. We have 3 files here.
MainWindow.xaml
<Window.Resources>
<CollectionViewSource x:Key="data1ViewSource" d:DesignSource="{d:DesignInstance {x:Type local:Data1}, CreateList=True}"/>
</Window.Resources>
<Grid>
<Button x:Name="TestButton" Content="Button" HorizontalAlignment="Left" Margin="78,25,0,0" VerticalAlignment="Top" Width="75" Click="TestButton_Click"/>
<TextBox x:Name="testStringTextBox" HorizontalAlignment="Left" Height="24" Margin="78,86,0,309" Grid.Row="0" Text="{Binding TestString}" VerticalAlignment="Center" Width="120"/>
<TextBlock x:Name="testStringTextBlock" HorizontalAlignment="Left" Margin="78,143,0,0" TextWrapping="Wrap" Text="{Binding TestString}" VerticalAlignment="Top" Height="181" Width="245"/>
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void TestButton_Click(object sender, RoutedEventArgs e)
{
Class1.TheButton();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
System.Windows.Data.CollectionViewSource data1ViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("data1ViewSource")));
// Load data by setting the CollectionViewSource.Source property:
// data1ViewSource.Source = [generic data source]
}
}
Class1.cs
public static class Class1
{
private static int Inc = 1;
public static Data1 testData = new Data1("Starting test\n");
public static void TheButton()
{
testData.TestString += "Test number " + Inc.ToString() + ".\n";
Inc++;
}
}
public class Data1 : INotifyPropertyChanged
{
public Data1(string input)
{
TestString = input;
}
private string _testString;
public event PropertyChangedEventHandler PropertyChanged;
public string TestString
{
get { return _testString; }
set { _testString = value; OnPropertyChanged("TestString"); }
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
The program compiles as is, without error, and when I set a breakpoint, I can view the object Class1.testData, and I can see the proper value in Class1.testData.TestString and _testString.
I have spent several days searching for some sort of simple example of what I'm trying to do here, and I have yet been able to find any solution to get it actually working. I've come to the conclusion that I must be doing something wrong or just plain missing something, but I also cannot find anything to give me insight on what that is.

Change the xaml to use x:Static and provide the Path:
<Canvas>
<Button x:Name="TestButton" Content="Button" HorizontalAlignment="Left" Margin="78,25,0,0" VerticalAlignment="Top" Width="75" Click="TestButton_Click"/>
<TextBox x:Name="testStringTextBox"
HorizontalAlignment="Left"
Height="24"
Margin="78,86,0,309"
Text="{Binding Source={x:Static local:Class1.testData}, Path=TestString}"
VerticalAlignment="Center"
Width="120"/>
<TextBlock x:Name="testStringTextBlock"
HorizontalAlignment="Left"
Margin="78,143,0,0"
TextWrapping="Wrap"
Text="{Binding Source={x:Static local:Class1.testData}, Path=TestString}"
VerticalAlignment="Top"
Height="181"
Width="245"/>
</Canvas>
Also note the if you enter a value in the textbox, the value will only be updated in your variable when the textbox loses focus.

Related

How to assign property (get; set;) to a WPF User Control?

I'm trying to learn property (get and set) in C#. I'm still new to the language and currently making a simple program using WPF with some textboxes, as you can see in the pictures.
So, here are the description :
Input : it's where user can type the input, located at MainWindow
Output 1 : it's where to see the typed string in input, located at MainWindow
Output 2 : same like Output 1, located inside tab one, at MainWindow.
Output 3 : same like Output 1, located inside tab two, still at MainWindow.
Output 4 : same like Output 1, located inside tab two, referred to local UserControl Page1.
Button : a button to "save" the input
Here are the codes :
Main Window : solution --> I added x:Name="Page1" after local:page1
<!-- MainWindow.xaml -->
<Window x:Class="TestGetSet.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:local="clr-namespace:TestGetSet"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TabControl x:Name="tabControl" HorizontalAlignment="Left" Height="212" Margin="37,20,0,0" VerticalAlignment="Top" Width="447">
<TabItem Header="TabItem">
<Grid Background="#FFE5E5E5">
<TextBox x:Name="output2" HorizontalAlignment="Left" Height="23" Margin="156,76,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120"/>
<Label x:Name="label2" Content="Output 2" HorizontalAlignment="Left" Margin="156,46,0,0" VerticalAlignment="Top"/>
</Grid>
</TabItem>
<TabItem Header="TabItem">
<Grid Background="#FFE5E5E5">
<TextBox x:Name="output3" HorizontalAlignment="Left" Height="23" Margin="321,26,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
<local:Page1 x:Name="Page1"/>
<Label x:Name="label3" Content="Output 3" HorizontalAlignment="Left" Margin="321,0,0,0" VerticalAlignment="Top"/>
</Grid>
</TabItem>
</TabControl>
<TextBox x:Name="input" Text=""/>
<TextBox x:Name="output1" Text=""/>
<Button x:Name="button" Content="Button" Click="button_Click"/>
<Label x:Name="label" Content="Input" HorizontalAlignment="Left" Margin="37,239,0,0" VerticalAlignment="Top"/>
<Label x:Name="label1" Content="Output 1" HorizontalAlignment="Left" Margin="364,239,0,0" VerticalAlignment="Top"/>
</Grid>
MainWindow code behind : solution --> added one line Page1.passingvalue(..)
// MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
public partial class MainWindow : Window
{
public myProperty myProp = new myProperty();
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
myProp.myData = input.Text;
output1.Text = myProp.myData;
output2.Text = myProp.myData;
output3.Text = myProp.myData;
Page1.passingvalue(myProp.myData);
}
}
Next is Page1.xaml (No changes made here for the solution)
<!-- Page1.xaml-->
<UserControl x:Class="TestGetSet.Page1"
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:TestGetSet"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox x:Name="output4" Text=""/>
<Label x:Name="label" Content="Output 4" HorizontalAlignment="Left" Margin="92,110,0,0" VerticalAlignment="Top"/>
</Grid>
</UserControl>
Page1 code behind : solution--> deleted timer and added passingvalue
// Page1.xaml.cs
using System;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Threading;
using System.ComponentModel;
namespace TestGetSet
{
public partial class Page1 : UserControl
{
private Thread _receiveThread;
myProperty myProp = new myProperty();
public Page1()
{
InitializeComponent();
/*DispatcherTimer MyTimer = new DispatcherTimer();
MyTimer.Interval = new TimeSpan(0, 0, 0, 0, 100);
MyTimer.Tick += MyTimer_Tick;
MyTimer.Start();*/
}
public void passingvalue(string m)
{
output4.Text = m;
}
/*private void MyTimer_Tick(object sender, EventArgs e)
{
output4.Text = myProp.myData;
}*/
}
}
Last one, the property, simple version :
// myProperty.cs
namespace TestGetSet
{
public class myProperty
{
public string myData { get; set }
}
}
Property with INotifyPropertyChanged :
// myProperty.cs
using System.ComponentModel;
namespace TestGetSet
{
public class myProperty : INotifyPropertyChanged
{
private string _textdata;
public string myData {
get
{
return _textdata;
}
set
{
_textdata = value;
NotifyPropertyChanged("myData");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Here are the screenshots of the program window :
one
and
two
As you can see, only Output4 is empty and the other outputs still give me results even though I don't use INotifyPropertyChanged. My question is why, and how can I fix this? I wonder if it's because I'm using UserControl for Page1, which the Output4 is in. I've been looking for the answer but came up with nothing. Any help is much appreciated. Thank you.
Okay so I updated the code. It's working now. I got the reference for passingvalue from : How to Pass a Value From a Window to a UserControl in WPF
Thank you.
Your issue here is that you misstakes the MainWindow's myProperty and Page1's myProperty.
In Page1, you set the output4.Text using myProp.myData which is wrong because myProp is the Page1's myProp which is never updated.
If you pass the MainWindow's reference to Page1 and instead write something like output4.Text = myMainWindowReferenc.myProp.myData; it will work as intended.
You could also add setting to output4 in your button_Click function by naming the Page1 in the XAML:
private void button_Click(object sender, RoutedEventArgs e)
{
myProp.myData = input.Text;
output1.Text = myProp.myData;
output2.Text = myProp.myData;
output3.Text = myProp.myData;
Page1.output4.Text = myProp.myData;
}

ComboBox Selected Item not updating

Problem
I am trying to bind a ComboBox's SelectedItem to a custom class but this does not update when the property is changed.INotifyPropertyChanged is implemented.
The DataContext
The DataContext is a custom class which contains many properties, but an extract of this is below. You can see it implements INotifyPropertyChanged and this called when the two properties are changed.
public class BctsChange : INotifyPropertyChanged
{
#region declarations
private byContact _Engineer;
public byContact Engineer
{
get { return _Engineer; }
set
{
_Engineer = value;
NotifyPropertyChanged("Engineer");
OnEngineerChanged();
}
}
private BctsSvc.DOSets _LeadingSet;
public BctsSvc.DOSets LeadingSet
{
get { return _LeadingSet; }
set { _LeadingSet = value; NotifyPropertyChanged("LeadingSet"); }
}
#endregion
#region INotify
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
public BctsChange()
{
Engineer = new byContact(Environment.UserName);
}
private void OnEngineerChanged()
{
if (Engineer != null)
{
BctsSvc.DOSets leadSet = GetLeadingSetFromDeptCode(Engineer.DeptCode);
if (leadSet == null) return;
LeadingSet = leadSet;
}
}
private static BctsSvc.DOSets GetLeadingSetFromDeptCode(string DeptCode)
{
BctsSvc.BctsServiceSoapClient svc = new BctsSvc.BctsServiceSoapClient();
BctsSvc.DOSets setX = svc.GetSetFromDeptCode(DeptCode);
return setX;
}
}
The Window XAML
I have several controls on the window, but to keep the code simple I believe the following extract will suffice.
<Window x:Class="MyNamespace.wdSubmit"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:MyNamespace"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Name="ucReqForm"
Title="wdSubmit" >
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
<GroupBox Header="Engineer Details" Name="grpOwnerDetails" >
<StackPanel Orientation="Vertical">
<Grid VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="35"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Engineer.FullName, FallbackValue='Please select an engineer by clicking →', Mode=OneWay}" Margin="5,0" IsEnabled="True" FontStyle="Italic" />
<Button Content="{StaticResource icoSearch}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Column="1" Height="23" Name="btnSelectEngineer" Margin="0,0,5,0" HorizontalAlignment="Stretch" ToolTip="Search for an engineer responsible" Click="btnSelectEngineer_Click" />
</Grid>
<ComboBox Height="23" x:Name="ddSet2" Margin="5,0" ItemsSource="{Binding LeadingSets, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" SelectedItem="{Binding LeadingSet, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,NotifyOnTargetUpdated=True}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SetName}" ToolTip="{Binding HelpInfo}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<my:LabelledDropdown Height="23" x:Name="ddSet" Margin="5,0" ItemsSource="{Binding LeadingSets, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" SelectedItem="{Binding LeadingSet, Mode=TwoWay,NotifyOnTargetUpdated=True,NotifyOnSourceUpdated=True}" Label="e.g. BodyHardware">
<my:LabelledDropdown.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SetName}" ToolTip="{Binding HelpInfo}"/>
</DataTemplate>
</my:LabelledDropdown.ItemTemplate>
</my:LabelledDropdown>
</StackPanel>
</GroupBox>
</StackPanel>
</Window>
The above extract contains:
A Label that contains a contact's name, and a button to search for a contact, bound to the FullName of the Engineer
A ComboBox that contains departments within the company, bound to an ObservableCollection<DOSets>, which contains a list of departments
Two ComboBoxes, one which is a custom one and the other which is temporary to ensure the bug is not within the control. These are Databound to LeadingSet
Window Code Behind
In the code behind I set the DataContext to CurrentChange. When the user wants to select a different Engineer then this will update the selected department for the engineer in CurrentChange.
When the user changes the engineer, the data binding for the engineer is updated, but the selected department (Leading Set) isn't.
//Usings here
namespace MyNamespace
{
public partial class wdSubmit : Window, INotifyPropertyChanged
{
private BctsSvc.BctsServiceSoapClient svc;
private BctsChange _CurrentChange;
public BctsChange CurrentChange
{
get { return _CurrentChange; }
set { _CurrentChange = value; OnPropertyChanged("CurrentChange"); }
}
private List<BctsSvc.DOSets> _LeadingSets;
public List<BctsSvc.DOSets> LeadingSets
{
get
{
return _LeadingSets;
}
}
public wdSubmit()
{
InitializeComponent();
svc = new BctsSvc.BctsServiceSoapClient();
_LeadingSets = svc.GetLeadSets().ToList();
OnPropertyChanged("LeadingSets");
this._CurrentChange = new BctsChange();
this.DataContext = CurrentChange;
CurrentChange.PropertyChanged += new PropertyChangedEventHandler(CurrentChange_PropertyChanged);
}
void CurrentChange_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged("CurrentChange");
OnPropertyChanged(e.PropertyName);
}
private void btnSelectEngineer_Click(object sender, RoutedEventArgs e)
{
byContact newContact = new frmSearchEngineer().ShowSearch();
if (newContact != null)
{
CurrentChange.Engineer = newContact;
PropertyChanged(CurrentChange, new PropertyChangedEventArgs("LeadingSet"));
PropertyChanged(CurrentChange.LeadingSet, new PropertyChangedEventArgs("LeadingSet"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(CurrentChange, new PropertyChangedEventArgs(propertyName));
}
}
}
I've realised the problem may be due to the LeadingSet, returned when the engineer is changed, being a different instance to that in the ObservableCollection.

Multibinding between 2 usercontrols

I have 2 usercontrols.
Usercontrol 1: The menubar which has buttons like Add, Edit, Delete, Save and Undo.
Usercontrol 2: Is a screen where the user can input text in textboxes and passwordboxes
But when I want to save I'm used to do the following when I only have 1 usercontrol which has the buttons and everything instead of the menubar and the detailscreen seperated:
<Button Style="{DynamicResource SaveButton}" Command="{Binding Path=SaveCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource pwConverter}">
<Binding ElementName="txtPassword" />
<Binding ElementName="txtRepeatPassword" />
</MultiBinding>
</Button.CommandParameter>
</Button>
But now the elementname "txtPassword" and "txtRepeatPassword" don't exist in that scope.
This is my SaveCommand when I click the save button. It receives those 2 parameters so I can check is the 2 passwords are the same and stuff like that.
private void SaveUserExecute(object passwords)
{
try
{
var passwordvalues = (object[])passwords;
PasswordBox passwordBox1 = (PasswordBox)passwordvalues[0];
PasswordBox passwordBox2 = (PasswordBox)passwordvalues[1];
...
Any ideas on how to solve this issue?
Because my 2 usercontrols shared the same DataContext I've made 2 properties which represent my PasswordBoxes. When I initialize that view I did the following:
public InputUserView()
{
InitializeComponent();
this.DataContext = InputUserViewModel.Instance;
InputUserViewModel.Instance.PasswordBox1 = txtPassword;
InputUserViewModel.Instance.PasswordBox2 = txtRepeatPassword;
}
So now my viewmodel has knowledge of those 2 passwordboxes. I think It's not really that good, but it works for me and I can live with it
This is easy if you use the MVVM pattern. You can have one ViewModel which can be the DataContext to each of your user controls, and your main Window. Then just bind to the properties on each of these.
Below is an example of a ViewModel, it has fields exposed by properties which we can bind to:
public class ViewModel : INotifyPropertyChanged
{
private readonly Command _command;
public Command Command
{
get { return _command; }
}
public ViewModel()
{
_command = new Command(this);
}
private string _textBoxOnUserControlOne;
private string _textBoxOnUserControlTwo;
public string TextBoxOnUserControlOne
{
get { return _textBoxOnUserControlOne; }
set
{
if (value == _textBoxOnUserControlOne) return;
_textBoxOnUserControlOne = value;
OnPropertyChanged("TextBoxOnUserControlOne");
}
}
public string TextBoxOnUserControlTwo
{
get { return _textBoxOnUserControlTwo; }
set
{
if (value == _textBoxOnUserControlTwo) return;
_textBoxOnUserControlTwo = value;
OnPropertyChanged("TextBoxOnUserControlTwo");
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Here is the command class, where I am going to work with both of these properties:
public class Command : ICommand
{
private readonly ViewModel _viewModel;
public Command(ViewModel viewModel)
{
_viewModel = viewModel;
}
public void Execute(object parameter)
{
var dataOnControlOne = _viewModel.TextBoxOnUserControlOne;
var dataOnControlTwo = _viewModel.TextBoxOnUserControlTwo;
//Use these values
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
Now, here is my first user control 1 which is bound to one of the fields on my ViewModel, notice the DataContext:
<UserControl ... DataContext="{StaticResource ViewModel}">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Text="{Binding TextBoxOnUserControlOne}" Margin="12,12,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" />
</Grid>
</UserControl>
And here is a second UserControl with the same DataContext, and the textbox is bound to a different property:
<UserControl ... DataContext="{StaticResource ViewModel}">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Text="{Binding TextBoxOnUserControlTwo}" Margin="12,12,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" />
</Grid>
</UserControl>
Here is my main window, which contains both of these user controls, and a button bound to my command class:
<Window ... DataContext="{StaticResource ViewModel}">
<Grid>
<my:UserControl1 HorizontalAlignment="Left" Margin="160,69,0,0" x:Name="userControl11" VerticalAlignment="Top" Height="47" Width="155" />
<my:UserControl2 HorizontalAlignment="Left" Margin="160,132,0,0" x:Name="userControl12" VerticalAlignment="Top" Height="48" Width="158" />
<Button Content="Button" Command="{Binding Command}" Height="23" HorizontalAlignment="Left" Margin="199,198,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
</Grid>
</Window>
And finally my App.Xaml class, to glue everything together:
<Application ...>
<Application.Resources>
<wpfApplication4:ViewModel x:Key="ViewModel"/>
</Application.Resources>
</Application>
Here, we have seperate user controls, and the fields are bound to properties on the one view model. This viewmodel passes itself into the command class, which can then access the properties which the textboxes on the seperate usercontrols are bound to, and work with them when the button is pressed. I hope this helps!

Dynamic user control change - WPF

I'm developing an app in WPF and I need to change in runtime a content of a ContentControl depending than the user selected on ComboBox.
I have two UserControls and at my combo exists two itens, corresponding each one each.
First usercontrol:
<UserControl x:Class="Validator.RespView"
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="167" d:DesignWidth="366" Name="Resp">
<Grid>
<CheckBox Content="CheckBox" Height="16" HorizontalAlignment="Left" Margin="12,12,0,0" Name="checkBox1" VerticalAlignment="Top" />
<ListBox Height="112" HorizontalAlignment="Left" Margin="12,43,0,0" Name="listBox1" VerticalAlignment="Top" Width="168" />
<Calendar Height="170" HorizontalAlignment="Left" Margin="186,0,0,0" Name="calendar1" VerticalAlignment="Top" Width="180" />
</Grid>
Second usercontrol:
<UserControl x:Class="Validator.DownloadView"
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="76" d:DesignWidth="354" Name="Download">
<Grid>
<Label Content="States" Height="28" HorizontalAlignment="Left" Margin="12,12,0,0" Name="label1" VerticalAlignment="Top" />
<ComboBox Height="23" HorizontalAlignment="Left" Margin="12,35,0,0" Name="comboBox1" VerticalAlignment="Top" Width="120" />
<RadioButton Content="Last 48 hs" Height="16" HorizontalAlignment="Left" Margin="230,42,0,0" Name="rdbLast48" VerticalAlignment="Top" />
<Label Content="Kind:" Height="28" HorizontalAlignment="Left" Margin="164,12,0,0" Name="label2" VerticalAlignment="Top" />
<RadioButton Content="General" Height="16" HorizontalAlignment="Left" Margin="165,42,0,0" Name="rdbGeral" VerticalAlignment="Top" />
</Grid>
At MainWindowView.xaml
<Window x:Class="Validator.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:du="clr-namespace:Validator.Download"
xmlns:resp="clr-namespace:Validator.Resp"
Title="Validator" Height="452" Width="668"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize">
<Window.Resources>
<DataTemplate DataType="{x:Type du:DownloadViewModel}">
<du:DownloadView/>
</DataTemplate>
<DataTemplate DataType="{x:Type resp:RespViewModel}">
<resp:RespView/>
</DataTemplate>
</Window.Resources>
<Grid>
<ComboBox ItemsSource="{Binding Path=PagesName}"
SelectedValue="{Binding Path=CurrentPageName}"
HorizontalAlignment="Left" Margin="251,93,0,0"
Name="cmbType"
Width="187" VerticalAlignment="Top" Height="22"
SelectionChanged="cmbType_SelectionChanged_1" />
<ContentControl Content="{Binding CurrentPageViewModel}" Height="171" HorizontalAlignment="Left" Margin="251,121,0,0" Name="contentControl1" VerticalAlignment="Top" Width="383" />
</Grid>
</Window>
I assigned to the DataContext of the MainView, the viewmodel below:
public class MainWindowViewModel : ObservableObject
{
#region Fields
private ICommand _changePageCommand;
private ViewModelBase _currentPageViewModel;
private ObservableCollection<ViewModelBase> _pagesViewModel = new ObservableCollection<ViewModelBase>();
private readonly ObservableCollection<string> _pagesName = new ObservableCollection<string>();
private string _currentPageName = "";
#endregion
public MainWindowViewModel()
{
this.LoadUserControls();
_pagesName.Add("Download");
_pagesName.Add("Resp");
}
private void LoadUserControls()
{
Type type = this.GetType();
Assembly assembly = type.Assembly;
UserControl reso = (UserControl)assembly.CreateInstance("Validator.RespView");
UserControl download = (UserControl)assembly.CreateInstance("Validator.DownloadView");
_pagesViewModel.Add(new DownloadViewModel());
_pagesViewModel.Add(new RespViewModel());
}
#region Properties / Commands
public ICommand ChangePageCommand
{
get
{
if (_changePageCommand == null)
{
_changePageCommand = new RelayCommand(
p => ChangeViewModel((IPageViewModel)p),
p => p is IPageViewModel);
}
return _changePageCommand;
}
}
public ObservableCollection<string> PagesName
{
get { return _pagesName; }
}
public string CurrentPageName
{
get
{
return _currentPageName;
}
set
{
if (_currentPageName != value)
{
_currentPageName = value;
OnPropertyChanged("CurrentPageName");
}
}
}
public ViewModelBase CurrentPageViewModel
{
get
{
return _currentPageViewModel;
}
set
{
if (_currentPageViewModel != value)
{
_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");
}
}
}
#endregion
#region Methods
private void ChangeViewModel(IPageViewModel viewModel)
{
int indexCurrentView = _pagesViewModel.IndexOf(CurrentPageViewModel);
indexCurrentView = (indexCurrentView == (_pagesViewModel.Count - 1)) ? 0 : indexCurrentView + 1;
CurrentPageViewModel = _pagesViewModel[indexCurrentView];
}
#endregion
}
On MainWindowView.xaml.cs, I wrote this event to do the effective change:
private void cmbType_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
MainWindowViewModel element = this.DataContext as MainWindowViewModel;
if (element != null)
{
ICommand command = element.ChangePageCommand;
command.Execute(null);
}
}
The app run ok and I inspected the application with WPFInspector and saw that the view changes when the combobox is changed internally, but the ContentControl still empty visually..
Sorry about the amount of code that I posted and my miss of knowledge but I'm working with this a long time and can't solve this problem.
Thanks
Issues:
Firstly don't ever create View related stuff in the ViewModel (UserControl). This is no longer MVVM when you do that.
Derive ViewModels from ViewModelBase and not ObservableObject unless you have a compelling reason to not use ViewModelBase when using MVVMLight. Keep ObservableObject inheritence for Models. Serves as a nice separation between VM's and M's
Next you do not need to make everything an ObservableCollection<T> like your _pagesViewModel. You do not have that bound to anything in your View's so it's just a waste. Just keep that as a private List or array. Check what a type actually does in difference to a similar other one.
Not sure about this one, maybe you pulled this code snippet as a demo, but do not use margins to separate items in a Grid. Your Layout is essentially just 1 Grid cell and the margins have the items not overlap. If you're not aware of that issue, Check into WPF Layout Articles.
Please don't forget principles of OOP, Encapsulation and sorts when writing a UI app. When having Properties like CurrentPageViewModel which you don't intend the View to switch make the property setter private to enforce that.
Don't resort to code-behind in the View too soon. Firstly check if it's only a View related concern before doing so. Am talking about your ComboBox SelectionChanged event handler. Your purpose of that in this demo is to switch the Bound ViewModel which is held in the VM. Hence it's not something that the View is solely responsible for. Thus look for a VM involved approach.
Solution:
You can get a working example of your code with the fixes for above from Here and try it out yourself.
Points 1 -> 5 are just basic straightforward changes.
For 6, I've created a SelectedVMIndex property in the MainViewModel which is bound to the SelectedIndex of the ComboBox. Thus when the selected index flips, the property setter after updating itself updates the CurrentPageViewModel as well such as
public int SelectedVMIndex {
get {
return _selectedVMIndex;
}
set {
if (_selectedVMIndex == value) {
return;
}
_selectedVMIndex = value;
RaisePropertyChanged(() => SelectedVMIndex);
CurrentPageViewModel = _pagesViewModel[_selectedVMIndex];
}
}

How to access objects in code from XAML

I am new to WPF and am trying to understand how to use data binding to bind the controls on my window to objects in my code behind. I see several questions about accessing XAML objects from the codebehind, but that's not what I'm looking for. I already know how to do that.
label1.Content = LabelText;
listbox1.ItemsSource = ListItems;
I have also seen answers about how to access a class in the codebehind from XAML.
<local:MyClass x:Key="myClass" />
But I don't see how to apply that to a specific instance of the class. Here is an example of what I'm trying to do. The 'Bindings' are obviously incorrect. That is what I need help with.
public partial class MainWindow : Window
{
private string _labelText;
private List<string> _listItems = new List<string>();
public MainWindow()
{
InitializeComponent();
_labelText = "Binding";
_listItems.Add("To");
_listItems.Add("An");
_listItems.Add("Object");
}
public string LabelText
{
get { return _labelText; }
set { _labelText = value; }
}
public List<string> ListItems
{
get { return _listItems; }
set { _listItems = value; }
}
}
<Window x:Class="SO_Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SO Demo" Height="160" Width="225">
<Grid DataContext="MainWindow">
<Label x:Name="label1" Width="80" Height="25" Margin="12,12,0,0"
Content="{Binding Path=LabelText}"
HorizontalAlignment="Left" VerticalAlignment="Top" />
<ListBox x:Name="listbox1" Width="100" Height="60" Margin="12,44,0,0"
ItemsSource="{Binding Path=ListItems}" DisplayMemberPath="ListItems"
HorizontalAlignment="Left" VerticalAlignment="Top" />
</Grid>
</Window>
The books and tutorials I have read make it sound like this should be very simple. What am I missing?
While you can DataBind directly to the class in the manner you're attempting, it is not how this is commonly done. The recommended approach is to create an object (ViewModel) that aggregates all the model data you want displayed in your UI, and then set that ViewModel as the DataContext of your View (Window in this case). I would recommend reading about MVVM, which is how most WPF application are built. But the example below can get you started.
Here is a simple example based on your sample above:
ViewModel
public class MyViewModel : INotifyPropertyChanged
{
private string _title;
private ObservableCollection<string> _items;
public string LabelText
{
get { return _title; }
set
{
_title = value;
this.RaisePropertyChanged("Title");
}
}
public ObservableCollection<string> ListItems {
get { return _items; }
set
{
_items = value; //Not the best way to populate your "items", but this is just for demonstration purposes.
this.RaisePropertyChanged("ListItems");
}
}
//Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
CodeBehind
public partial class MainWindow : Window
{
private MyViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new MyViewModel();
//Initialize view model with data...
this.DataContext = _viewModel;
}
}
View (Window)
<Window x:Class="SO_Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SO Demo" Height="160" Width="225">
<Grid>
<Label x:Name="label1" Width="80" Height="25" Margin="12,12,0,0" Content="{Binding Path=LabelText}"
HorizontalAlignment="Left" VerticalAlignment="Top" />
<ListBox x:Name="listbox1" Width="100" Height="60" Margin="12,44,0,0"
ItemsSource="{Binding Path=ListItems}"
HorizontalAlignment="Left" VerticalAlignment="Top" />
</Grid>
</Window>
<Grid DataContext="MainWindow"> is invalid.
If you want to reference the window you must either:
<Window x:Name="MyWindow">
<Grid DataContext="{Binding ElementName=MyWindow}"/>
</Window>
or
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>

Categories