I'm still pretty new to WPF and I decided to change the application I am developing to start following the MVVM pattern as best as I could. I am running into a problem when I try to have a list box dictate the view model of a content control. I've been stuck on this for a while and searching the internet is not producing answers for me.
For some reason a new instance of the view model the list box contains is being generated as the data context of the content control. When I was debugging I made sure that the list box contains the view models it should, and that the item I select on the list box is indeed the item that the list box is selecting, however the content control changing based on the selection. There is a view model populating the content control, however it is not in the collection the list box populates from. And I can somehow delete the view model in the content control via my remove button. But when I make a selection change on the list box, or add a new item to the collection it populates the content control with a new view model that once again is not in the collection. I have no clue why it is doing this, or what in my code would suggest this behavior.
I made a simple application to try and figure out what I'm doing wrong. It replicates my problem perfectly. I'm pretty sure the buttons don't adhere to MVVVM (supposed to run a command contained in the view model to adhere to MVVM from what I've been reading) but that is not my main concern right now as the problem exists without the buttons.
MainWindow.xml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1" x:Class="WpfApplication1.MainWindow"
Title="MainWindow" Height="440" Width="436">
<Window.DataContext>
<local:mwvm/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type local:ucvm}">
<local:uc/>
</DataTemplate>
</Window.Resources>
<Grid>
<Button Content="a" HorizontalAlignment="Left" Margin="323,351,0,0" VerticalAlignment="Top" Width="95" Click="Button_Click"/>
<Button Content="r" HorizontalAlignment="Left" Margin="323,378,0,0" VerticalAlignment="Top" Width="95" Click="Button_Click_1"/>
<ContentControl Margin="10,10,110,10" Content="{Binding SelectedItem, ElementName=lb_UCs}"/>
<ListBox x:Name="lb_UCs" HorizontalAlignment="Left" Height="336" Margin="323,10,0,0" VerticalAlignment="Top" Width="95" ItemsSource="{Binding UCs}" DisplayMemberPath="CoolText"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class PanelPartsView : UserControl
{
private PanelPartsViewModel _DC;
public PanelPartsView()
{
InitializeComponent();
_DC = DataContext as PanelPartsViewModel;
}
private void btn_Remove_Click(object sender, RoutedEventArgs e)
{
_DC.Panels.Remove(lb_Panels.SelectedItem as PartsViewModel);
}
private void btn_Add_Click(object sender, RoutedEventArgs e)
{
var pvm = new PartsViewModel();
_DC.Panels.Add(pvm);
lb_Panels.SelectedItem = pvm;
System.Console.WriteLine("lb_Panels.selecteditem = {0}", ((PartsViewModel)lb_Panels.SelectedItem).PanelName);
System.Console.WriteLine("cc_PanelParts.content = {0}", ((PartsViewModel)cc_PanelParts.Content).PanelName);
}
}
mwvm
class mwvm
{
private ObservableCollection<ucvm> _UCs = new ObservableCollection<ucvm>();
public ObservableCollection<ucvm> UCs
{
get { return _UCs; }
}
public mwvm()
{
//this is for for testing, the real application would be purely dynamic
_UCs.Add(new ucvm());
_UCs.Add(new ucvm());
_UCs.Add(new ucvm());
}
}
uc.xaml
<UserControl
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:WpfApplication1" x:Class="WpfApplication1.uc"
mc:Ignorable="d" d:DesignWidth="300" Height="90">
<Grid>
<Grid.DataContext>
<local:ucvm/>
</Grid.DataContext>
<Button Content="{Binding CoolText}" Margin="10,10,10,0" Height="44" VerticalAlignment="Top"/>
<TextBox Height="23" Margin="10,59,10,0" TextWrapping="Wrap" Text="{Binding CoolText}" VerticalAlignment="Top"/>
</Grid>
</UserControl>
uc.xaml.cs
public partial class uc : UserControl
{
public uc()
{
InitializeComponent();
}
}
ucvm.cs
class ucvm : INotifyPropertyChanged
{
private static int i = 1;
private string _CoolText = "<" + i++ + ">" + System.DateTime.Now.ToLongTimeString();
public string CoolText
{
get { return _CoolText; }
set
{
_CoolText = value;
NPC("CoolText");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NPC(string s)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(s));
}
}
I have also tried binding the content control like so...
<ContentControl Content="{Binding SelectedUCVMl, Mode=OneWay}"/>
<ListBox x:Name="lb_UCs" ItemsSource="{Binding UCs}" SelectedItem="{Binding SelectedUCVM}" DisplayMemberPath="CoolText"/>
...and so...
<ContentControl Content="{Binding UCs/}"/>
<ListBox x:Name="lb_UCs" ItemsSource="{Binding UCs}" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="CoolText"/>
but to no avail.
Any help would be greatly appreciated.
It looks like you just have to remove this part from uc.xaml:
<Grid.DataContext>
<local:ucvm/>
</Grid.DataContext>
This syntax creates a new instance of the view model, each time an instance of uc.xaml is created, which of course isn't what you want. You want the data context of uc.xaml instances to inherit the instance currently selected in the list box.
Related
I get this error:- System.NullReferenceException: 'Object reference not set to an instance of an object.'
objectPlacement was null.
private void Button_Click(object sender, RoutedEventArgs e)
{
ObjectPlacement w = new ObjectPlacement() {Topmost = };// ObjectPlacement is new WPF window
objectPlacement.WindowStyle = WindowStyle.None;
settingpanel.Children.Add(objectPlacement);//settingpanel stack is panel name
w.Show();
}
It would be much more usual to define a usercontrol or datatemplate for whatever you're trying to show in your window. A window is a kind of content control. One way to think of a window ( or contentcontrol ) is something that shows you some UI. All the UI in a window is that content.
When you add window to a project it is templated out with a grid in it.
This is the content and everything you want to see in that window goes in it.
You could replace that grid with something else instead.
If you made that a contentpresenter then you can bind or set what that'll show to some encapsulated re-usable UI.
Usually the best way to encapsulate re-usable UI is as a usercontrol.
A datatemplate can reference a usercontrol.
It is not usually your entire UI for a window you want to switch out. But you can and that is occasionally useful - say if you want a generic way to show dialogs.
The usual way to write wpf is mvvm so most devs will want some mvvm way of switching out UI.
I'll show you some code might make the description clearer.
There are some corners cut in what follows, so this is illustrative. Don't just run with this for your next lead developer interview at a stock traders.
But, basically you click a button for Login you "navigate" to a LoginUC view. Click a button for User and you "navigate" to UserUC.
My mainwindow.
<Window.Resources>
<DataTemplate DataType="{x:Type local:LoginViewModel}">
<local:LoginUC/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:UserViewModel}">
<local:UserUC/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ItemsControl ItemsSource="{Binding NavigationViewModelTypes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding VMType}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ContentPresenter Grid.Column="1"
Content="{Binding CurrentViewModel}"
/>
</Grid>
</Window>
Notice the datatemplates which associate the type of a viewmodel with a usercontrol.
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/data-templating-overview?view=netframeworkdesktop-4.8
What will happen is you present your data in a viewmodel to the UI via that contentpresenter and binding. That viewodel is then templated out into UI with your viewmodel as it's datacontext. The datacontext of a UserUC view will therefore be an instance of UserViewModel. Change CurrentViewModel to an instance of LoginViewModel and you get a LoginUC in your mainwindow instead.
The main viewmodel.
public class MainWindowViewModel : INotifyPropertyChanged
{
public string MainWinVMString { get; set; } = "Hello from MainWindoViewModel";
public ObservableCollection<TypeAndDisplay> NavigationViewModelTypes { get; set; } = new ObservableCollection<TypeAndDisplay>
(
new List<TypeAndDisplay>
{
new TypeAndDisplay{ Name="Log In", VMType= typeof(LoginViewModel) },
new TypeAndDisplay{ Name="User", VMType= typeof(UserViewModel) }
}
);
private object currentViewModel;
public object CurrentViewModel
{
get { return currentViewModel; }
set { currentViewModel = value; RaisePropertyChanged(); }
}
private RelayCommand<Type> navigateCommand;
public RelayCommand<Type> NavigateCommand
{
get
{
return navigateCommand
?? (navigateCommand = new RelayCommand<Type>(
vmType =>
{
CurrentViewModel = null;
CurrentViewModel = Activator.CreateInstance(vmType);
}));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Type and display relates the type for a viewmodel with text displayed in the UI.
public class TypeAndDisplay
{
public string Name { get; set; }
public Type VMType { get; set; }
}
This is "just" quick and dirty code to illustrate a principle which is usually called viewmodel first navigation. Google it, you should find a number of articles explaining it further.
For completeness:
<UserControl x:Class="wpf_Navigation_ViewModelFirst.LoginUC"
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:wpf_Navigation_ViewModelFirst"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel Background="Yellow">
<TextBlock Text="This is the Login User Control"/>
<TextBox>
<TextBox.InputBindings>
<KeyBinding Key="Return" Command="{Binding LoginCommand}"/>
</TextBox.InputBindings>
</TextBox>
</StackPanel>
</UserControl>
public class LoginViewModel
{
private RelayCommand loginCommand;
public RelayCommand LoginCommand
{
get
{
return loginCommand
?? (loginCommand = new RelayCommand(
() =>
{
string s = "";
}));
}
}
}
<UserControl x:Class="wpf_Navigation_ViewModelFirst.UserUC"
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:wpf_Navigation_ViewModelFirst"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Background="pink">
<TextBlock Text="This is the User module Control"
VerticalAlignment="Top"
/>
<TextBlock Text="{Binding Path=DataContext.MainWinVMString, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
VerticalAlignment="Bottom"
/>
</Grid>
</UserControl>
public class UserViewModel
{
}
I put this together some years ago, I would now recommend the community mvvm toolkit with it's code generation, base classes, messenger etc.
I have a window whose properties and child element properties bind to a class called Data:
public TerminalOverlay(Data dataContext)
{
DataInstance = dataContext;
DataContext = DataInstance;
InitializeComponent();
}
The window TerminalOverlay is created in my MainWindow window, as follows:
public void MainWindow_Loaded(object sender, EventArgs e)
{
_terminalOverlayWindow = new TerminalOverlay(_dataInstance);
_terminalOverlayWindow.Owner = this;
_terminalOverlayWindow.Show();
}
_dataInstance is instantiated in the constructor of MainWindow, and one of the "problem" properties in it is the following:
public double ? PosX
{
get
{
return _posX == null ? _defaultPosX : _posX;
}
set
{
_posX = value;
OnPropertyChanged("PosX");
}
}
Where OnPropertyChanged is implemented as follows:
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The Data object is passed from the main window which creates TerminalOverlay. The Data object also implements INotifyPropertyChanged, so when I update properties in the Data object from the main window, they are reflected in the TerminalOverlay window.
However, this is only the case for the child elements of the TerminalOverlay window. The properties of the TerminalOverlay window itself are initially set to the values stored in the Data class, but they do not seem to update, even though the child elements do.
What am I doing wrong? Looking in the visual tree I found that TerminalOverlay.DataContext.TopX did update, it's just that the window isn't being notified to update.
Also, TerminalOverlay.xaml looks like the following:
<Window x:Class="Background_Terminal.TerminalOverlay"
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:Background_Terminal"
mc:Ignorable="d"
Title="TerminalOverlay" Height="200" Width="800" Left="{Binding PosX, Mode=OneWay}" Top="{Binding PosY, Mode=OneWay}" AllowsTransparency="True" WindowStyle="None" ResizeMode="NoResize" Background="Transparent" Loaded="TerminalOverlay_Loaded">
<Grid>
<TextBox x:Name="TerminalData_TextBox" BorderThickness="0" FontFamily="Consolas" Background="Transparent" IsReadOnly="True" IsReadOnlyCaretVisible="True" FontSize="{Binding FontSize}" Foreground="{Binding FontColor}" Text="{Binding TerminalDataDisplay, Mode=OneWay}"/>
<TextBox x:Name="Input_TextBox" VerticalAlignment="Bottom" FontSize="{Binding FontSize}" Foreground="{Binding FontColor}" />
</Grid>
</Window>
The properties like FontSize in Input_TextBox update properly, but Top and Left in the Window properties do not.
your posX is a Nullable variable : they have "special" binding art
try this here
Left="{Binding PosX, Mode=TwoWay, TargetNullValue=''}"
I have to build a custom control. Simple TextBox with built-in validation which I can use in different parts of my app.
I did it this way:
I created new custom control (call it ValidTextBox) derived directly from TextBox;
It has its viewmodel (ValidTextBoxVM) with simple validation logic.
The Text property of the control is binded to Number property of the viewmodel.
ValidTextBoxVM code:
public class ValidTextBoxVM : INotifyPropertyChanged
{
#region INotifyPropertyChange implementation
private String _number;
public String Number
{
get { return _number; }
set
{
if (_number != value)
{
Validate(value);
_number = value;
RaisePropertyChanged("Number");
}
}
}
private void Validate(string number)
{
if (!string.IsNullOrEmpty(number) && number.Length > 10)
{
throw new ArgumentOutOfRangeException("Number too long.");
}
}
}
ValidTextBox.xaml code:
<TextBox x:Class="WpfApplication1.ValidTextBox"
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:vm="clr-namespace:WpfApplication1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Text="{Binding Number, ValidatesOnExceptions=True, UpdateSourceTrigger=LostFocus}">
<TextBox.DataContext>
<vm:ValidTextBoxVM/>
</TextBox.DataContext>
</TextBox>
I put my control on MainWindow and it worked perfectly. While losting focus - ViewModel raised the exception if validation process didn't pass - that's OK (code below).
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctrl="clr-namespace:WpfApplication1"
xmlns:vm="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ctrl:ValidTextBox Margin="5" Width="200" HorizontalAlignment="Left"/>
</StackPanel>
</Window>
The situation changed when I use seperate viewmodel for my MainWindow (MainWindowVM) and bind Text property of my control with field (MainNumber) in a MainWindowVM.
It's hidden my previous binding and validation has stopped to work (code below).
<StackPanel>
<ctrl:ValidTextBox Text="{Binding MainNumber, ValidatesOnExceptions=True, UpdateSourceTrigger=LostFocus}" Margin="5" Width="200" HorizontalAlignment="Left"/>
</StackPanel>
Is there any pattern that makes creating of self-validating controls possible. I found many solutions but with validation process outside the control.
The problem is setting DataContext on your TextBox. This means that when you write:
<ctrl:ValidTextBox Text="{Binding MainNumber
... the framework will attempt to resolve "MainNumber" on the ctrl:ValidTextBox object, which is the object's DataContext. (It's counter-intuitive, but that is how it works -- you should be able to see a binding error along the lines of "cannot find property "MainNumber" on the object "ValidTextBox", if you check the Visual Studio "Output" window.)
I have found that using control-specific view models is tricky in general, and leads to complications. I suggest avoiding that approach where possible. In this case, why not just extend TextBox and add a validation handler to the LostFocus event?
public class ValidTextBox : TextBox
{
public ValidTextBox()
{
LostFocus += ValidTextBox_LostFocus;
}
void ValidTextBox_LostFocus(object sender, System.Windows.RoutedEventArgs e)
{
// TODO validate
}
}
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];
}
}
I know a way to do MVC binding of one string to one TextBox. That's how it can be done:
C#:
namespace WpfApplication4
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = Model;
}
public ModelClass Model = new ModelClass();
private void button1_Click(object sender, RoutedEventArgs e)
{
Model.Output += "Setting New Output! ";
}
public class ModelClass : INotifyPropertyChanged
{
string _output;
public event PropertyChangedEventHandler PropertyChanged =
delegate { };
public string Output
{
get { return _output; }
set { _output = value;
PropertyChanged(this,
new PropertyChangedEventArgs("Output"));
}
}
}
}
}
XAML:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="Button" VerticalAlignment="Top"
Name="button1" Click="button1_Click" />
<TextBox VerticalAlignment="Bottom"
Name="textBox1" Text="{Binding Path=Output}" />
</Grid>
</Window>
But I can't find a way to bind a two-dimensional array (or List) to a Grid or DataGrid. Can you help me with it? I couldn't find a working example on SO.
Consider using a DataGrid to display your two-dimensional array, assuming you can store your data as a List<ColumnData> where ColumnData is a class with one property per table column.
The WPF SDK contains a DataGrid, and there are several data grids from vendors available that have additional features.
if you wanna bind data to a datagrid you should be read something about the following.
ICollectionView, BindingListCollectionView
if you have somekind of collection you simply set the itemssource.
<DataGrid ItemsSource="{Binding Path=MyCollection, Mode=OneWay}" />
Collection types are mostly ObservableCollection or DataSet/DataTable. if your collection supports editing and so on, you can do it with the datagrid.