Changing Content Type Questions - c#

This question has been answered a few times but I can't seem to put the solution together. What I have below is what I'm glued together through various forums. I'm also new to WPF. I'm trying to switch the content of the MainWindow.xaml based some parameters. What I have:
<Window.Resources>
<DataTemplate x:Key="LogsGriDataTemplate" DataType="{x:Type viewModel:ViewModel1}">
<Label>This is a log</Label>
</DataTemplate>
<DataTemplate x:Key="ReportsGridDataTemplate" DataType="{x:Type viewModel:ViewModel2}">
<Label>This is a report</Label>
</DataTemplate>
</Window.Resources>
<ContentControl Grid.Row="1" Grid.Column="0" Content="{Binding CurrentPageViewModel}" />
private ViewModel1 _viewModel1 = new ViewModel1();
private ViewModel2 _viewModel2 = new ViewModel2();
private DataTemplate _CurrentPageViewModel;
public DataTemplate CurrentPageViewModel
{
get { return _CurrentPageViewModel; }
set { Set(() => CurrentPageViewModel, ref _CurrentPageViewModel, value); }
}
public void OnButtonPressMethod(object param)
{
if (view == 0)
{
CurrentPageViewModel = _viewModel1;
}
else
{
CurrentPageViewModel = _viewModel1;
}
}
The compiler is complaining about the CurrentPageViewModel = _viewModel1/2 statement saying you cannot set type ViewModel to type DataTemplate which makes sense. What should the CurrentPageViewModel property be? Is there anything else wrong with this code? Thanks.

The binding source should be your view model, not a DataTemplate. The DataTemplate with DataType definition in XAML will automatically bind content to the data template that matches the type.
So you could create a common interface/base class for view models 1 & 2:
public interface IViewModel { }
public class ViewModel1 : IViewModel { }
public class ViewModel2 : IViewModel { }
private IViewModel _viewModel1 = new ViewModel1();
private IViewModel _viewModel2 = new ViewModel2();
private IViewModel _CurrentPageViewModel;
public IViewModel CurrentPageViewModel
{
get { return _CurrentPageViewModel; }
set { Set(() => CurrentPageViewModel, ref _CurrentPageViewModel, value); }
}

Related

Bind property between sibling user controls

I have the below problem: I have two different user controls inside a parent user control. These are trainList, which holds a list of train objects and trainView, which is an user control that shows details of the selected train in the list.
My wish is to share a variable of trainList with trainView.
What I have now is:
Parent user control:
<UserControl>
<UserControl>
<customControls:trainList x:Name="trainList"></customControls:trainList>
</UserControl>
<UserControl>
<customControls:trainView x:Name="trainView"></customControls:trainView>
</UserControl>
<TextBlock DataContext="{Binding ElementName=trainList, Path=SelectedTrain}" Text="{ Binding SelectedTrain.Id }">Test text</TextBlock>
</UserControl>
TrainList class:
public partial class TrainList : UserControl
{
public TrainList()
{
this.InitializeComponent();
this.DataContext = this;
}
public Train SelectedTrain { get; set; }
public void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Debug.Print(this.SelectedTrain.Id);
}
}
Note: The Train class implements INotifyPropertyChanged.
If I got this to work, I'd apply the binding to the trainView user control (not sure if this would work) instead to the text block.
<UserControl>
<customControls:trainView x:Name="trainView" DataContext="{Binding ElementName=trainList, Path=SelectedTrain}"></customControls:trainView>
</UserControl>
And then, I would access that variable someway from the code-behind of trainView.
(And after this, I would like to share a different variable from trainView with its parent user control, but maybe that's another question).
My current question is: could this be done this way or would I need to follow another strategy?
Take this simple view model, with a base class that implements the INotifyPropertyChanged interface, and a Train, TrainViewModel and MainViewModel class.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(
ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (!Equals(storage, value))
{
storage = value;
OnPropertyChanged(propertyName);
}
}
}
public class Train : ViewModelBase
{
private string name;
public string Name
{
get { return name; }
set { SetValue(ref name, value); }
}
private string details;
public string Details
{
get { return details; }
set { SetValue(ref details, value); }
}
// more properties
}
public class TrainViewModel : ViewModelBase
{
public ObservableCollection<Train> Trains { get; }
= new ObservableCollection<Train>();
private Train selectedTrain;
public Train SelectedTrain
{
get { return selectedTrain; }
set { SetValue(ref selectedTrain, value); }
}
}
public class MainViewModel
{
public TrainViewModel TrainViewModel { get; } = new TrainViewModel();
}
which may be initialized in the MainWindow's constructor like this:
public MainWindow()
{
InitializeComponent();
var vm = new MainViewModel();
DataContext = vm;
vm.TrainViewModel.Trains.Add(new Train
{
Name = "Train 1",
Details = "Details of Train 1"
});
vm.TrainViewModel.Trains.Add(new Train
{
Name = "Train 2",
Details = "Details of Train 2"
});
}
The TrainDetails controls would look like this, of course with more elements for more properties of the Train class:
<UserControl ...>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Details}"/>
</StackPanel>
</UserControl>
and the parent UserControl like this, where I directly use a ListBox instead of a TrainList control:
<UserControl ...>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Trains}"
SelectedItem="{Binding SelectedTrain}"
DisplayMemberPath="Name"/>
<local:TrainDetailsControl Grid.Column="1" DataContext="{Binding SelectedTrain}"/>
</Grid>
</UserControl>
It would be instantiated in the MainWindow like this:
<Grid>
<local:TrainControl DataContext="{Binding TrainViewModel}"/>
</Grid>
Note that in this simple example the elements in the UserControls' XAML bind directly to a view model instance that is passed via their DataContext. This means that the UserControl know the view model (or at least their properties). A more general approach is to declare dependency properties in the UserControl class, that are bound to view model properties. The UserControl would then be independent of any particular view model.

NotifyPropertyChanged and ContentControl

I am struggling for one week now with my problem and i cant solve it. I am programming a MVVM WPF application which is having one window (MainView). In This Mainview i want to load different UserControl when i need them. In the Application-Startup I am loading the MainViewModel. In the Constructor of the MainViewModel I am loading the First ViewModel (LoginViewModel). Cause of my MainView.xaml it is showing me my Login-User-Control like i want to. So till this point everything is fine. In the ActivePanel-class i am saving the CurrentView, because in my MainView.xaml i am making a binding to my CurrentView for the ContentControl. So everything is working except the changing of the views although my NotifyPropertyChanged method of the CurrentView is working. I am thinking, that my mistake is in the xaml (DataBinding). Hope you guys can help me.
This is my MainView.xaml in which i want to load the different DataTemplates. Like I said before: The loading of the LoginViewModel via the Constructor of MainViewModel is working. The changing to other VieModels is working as well, but the DataBinding to the ContentControl is the big problem here.
<Window x:Class="App.View.MainView"
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:App.View"
mc:Ignorable="d"
xmlns:viewmodels="clr-namespace:App.ViewModels"
xmlns:views="clr-namespace:App.View"
xmlns:helper="clr-namespace:App.Helper"
Title="Betrooms" Height="500" Width="350">
<Window.Resources>
<DataTemplate x:Name="LoginUCTemplate" DataType="{x:Type viewmodels:LoginViewModel}">
<views:LoginUC DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="RegUCTemplate" DataType="{x:Type viewmodels:RegViewModel}">
<views:RegUC DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="HomeUCTemplate" DataType="{x:Type viewmodels:HomeViewModel}">
<views:HomeUC DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<viewmodels:ActivePanel/>
</Window.DataContext>
<Grid>
<ContentControl Content="{Binding CurrentView, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/>
</Grid>
</Window>
This is the class of my ActivePanel where i am saving the information about which ViewModel is the active one. The CurrentView is the property I am binding the Content Control to.
namespace APP.ViewModels
{
public class ActivePanel : NotifyPropertyChanged
{
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
if (value != _currentView)
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
}
}
}
This is my MainViewModel:
namespace App.ViewModels
{
public class MainViewModel : ActivePanel
{
private LoginViewModel _loginViewModel;
public MainViewModel()
{
_loginViewModel = new LoginViewModel();
CurrentView = _loginViewModel;
}
}
}
And this is my LoginViewModel where I am changing the value of CurrentView via an action:
namespace App.ViewModels
{
public class LoginViewModel : ActivePanel
{
#region Member
private string _username;
private string _password;
bool login = false;
private HomeViewModel _homeViewModel;
private RegViewModel _regViewModel;
UserModel User = new UserModel();
#endregion
#region Properties
public ICommand RegActionCommand { get; set; }
public ICommand LogActionCommand { get; set; }
public string GetUsername
{
get { return _username; }
set
{
if (value != _username)
{
_username = value;
OnPropertyChanged("GetUsername");
}
}
}
public string GetPassword
{
get { return _password; }
set
{
if (value != _password)
{
_password = value;
OnPropertyChanged("GetPassword");
}
}
}
#endregion
#region Constructor
public LoginViewModel()
{
this.RegActionCommand = new RelayCommand(RegAction);
this.LogActionCommand = new RelayCommand(LogAction);
}
#endregion
#region Button-Action
private void LogAction(object obj)
{
_homeViewModel = new HomeViewModel();
CurrentView = _homeViewModel;
}
private void RegAction(object obj)
{
_regViewModel = new RegViewModel();
CurrentView = _regViewModel;
}
#endregion
}
}
I hope my question is understandable: The ContenControl binding is set to CurrentView but the ContentControl is never changing although the property of CurrentView is changing.
Thanks to you all. Cheers, Paul.
In your command handler, you are changing the CurrentView property of your LoginViewModel. The ContentControl's DataContext is the MainViewModel though, so it's content is bound to the CurrentView property of the MainViewModel.
You need to set the MainViewModel's property in your command handler. There are different ways of achieving this, for example you could add a constructor parameter to the LoginViewModel to pass a reference to the MainViewModel. You can save this reference and then access it in your command handler.
Another possibilty would be to raise an event or send a message from the command in your LoginViewModel and handle it in the MainViewModel. This would reduce the coupling between your ViewModels, but depending on which mechanism and library you use it might be a little bit more complicated.

How to use ReactiveUI with a Hierarchical Data Source (Tree view)

I've figured out a way to bind user controls inside a tree view dynamically with ReactiveUI.
But ...
The top level binding to the HierachicalDataSource is in the XAML not the code behind, and I need to set the ItemsSource directly and not use this.OneWayBind per the general pattern for ReactiveUI binding.
So, my question is: did I miss something in the ReactiveUI framework that would let me bind with this.OneWayBind and move the HierachicalDataTemplete into the code behind or a custom user control?
In particular- Is there another overload of OneWayBind supporting Hierarchical Data Templates, or a way to suppress the data template generation for the call when using it?
Update
I've added selected item, and programatic support for Expand and Selected to my test project, but I had to add a style to the XAML. I'd like to replace that with a simple RxUI Bind as well. Updated the examples.
Here are the key details:
Tree Control in Main View
<TreeView Name="FamilyTree" >
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
<HierarchicalDataTemplate DataType="{x:Type local:TreeItem}" ItemsSource="{Binding Children}">
<reactiveUi:ViewModelViewHost ViewModel="{Binding ViewModel}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
main view code behind
public partial class MainWindow : Window, IViewFor<MainVM>
{
public MainWindow()
{
InitializeComponent();
//build viewmodel
ViewModel = new MainVM();
//Register views
Locator.CurrentMutable.Register(() => new PersonView(), typeof(IViewFor<Person>));
Locator.CurrentMutable.Register(() => new PetView(), typeof(IViewFor<Pet>));
//NB. ! Do not use 'this.OneWayBind ... ' for the top level binding to the tree view
//this.OneWayBind(ViewModel, vm => vm.Family, v => v.FamilyTree.ItemsSource);
FamilyTree.ItemsSource = ViewModel.Family;
}
...
}
MainViewModel
public class MainVM : ReactiveObject
{
public MainVM()
{
var bobbyJoe = new Person("Bobby Joe", new[] { new Pet("Fluffy") });
var bob = new Person("Bob", new[] { bobbyJoe });
var littleJoe = new Person("Little Joe");
var joe = new Person("Joe", new[] { littleJoe });
Family = new ReactiveList<TreeItem> { bob, joe };
_addPerson = ReactiveCommand.Create();
_addPerson.Subscribe(_ =>
{
if (SelectedItem == null) return;
var p = new Person(NewName);
SelectedItem.AddChild(p);
p.IsSelected = true;
p.ExpandPath();
});
}
public ReactiveList<TreeItem> Family { get; }
...
}
TreeItem base class
public abstract class TreeItem : ReactiveObject
{
private readonly Type _viewModelType;
bool _isExpanded;
public bool IsExpanded
{
get { return _isExpanded; }
set { this.RaiseAndSetIfChanged(ref _isExpanded, value); }
}
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set { this.RaiseAndSetIfChanged(ref _isSelected, value); }
}
private TreeItem _parent;
protected TreeItem(IEnumerable<TreeItem> children = null)
{
Children = new ReactiveList<TreeItem>();
if (children == null) return;
foreach (var child in children)
{
AddChild(child);
}
}
public abstract object ViewModel { get; }
public ReactiveList<TreeItem> Children { get; }
public void AddChild(TreeItem child)
{
child._parent = this;
Children.Add(child);
}
public void ExpandPath()
{
IsExpanded = true;
_parent?.ExpandPath();
}
public void CollapsePath()
{
IsExpanded = false;
_parent?.CollapsePath();
}
}
Person Class
public class Person : TreeItem
{
public string Name { get; set; }
public Person(string name, IEnumerable<TreeItem> children = null)
: base(children)
{
Name = name;
}
public override object ViewModel => this;
}
person view user control
<UserControl x:Class="TreeViewInheritedItem.PersonView"... >
<StackPanel>
<TextBlock Name="PersonName"/>
</StackPanel>
</UserControl>
Person view code behind
public partial class PersonView : UserControl, IViewFor<Person>
{
public PersonView()
{
InitializeComponent();
this.OneWayBind(ViewModel, vm => vm.Name, v => v.PersonName.Text);
}
...
}
Pet work the same as person.
And the full project is here ReactiveUI Tree view Sample
I reviewed the ReactiveUI source and this is the only way to do this. The Bind helper methods always use a DataTemplate and not a HierarchicalDataTemplate.
So this approach will use the XAML binding for the very top level and then let you use ReactiveUI binding on all of the TreeView items.
I'll see about creating a pull request to handle this edge case.
Thanks,
Chris

MvvmLight too much messages

I am switching two Views. after message sending from Messenger.Default.Send<Message>(new Message {LoadingIndication="Loaded" },"Token"); It's reciving two message's because it create's OneViewModel two times first time in BinaryMultiViewModel second into OneView. But i need only one message. I can not remove something because in first case it should not switch in second it should show data.
For example
MultiView.cs
namespace Test.ViewModel
{
class BinaryMultiViewModel : ViewModelBase
{
readonly static OneViewModel OneViewModel = new OneViewModel();
readonly static FourViewModel FourViewModel = new FourViewModel();
private ViewModelBase currentMultiViewModel;
public BinaryMultiViewModel()
{
currentMultiViewModel = BinaryMultiViewModel.OneViewModel;
}
public ViewModelBase CurrentMultiViewModel
{
get
{
return currentMultiViewModel;
}
set
{
if (currentMultiViewModel == value)
{
return;
}
currentMultiViewModel = value;
RaisePropertyChanged("CurrentMultiViewModel");
}
}
}
}
MultiView.xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<ContentControl Grid.Column="0" Content="{Binding CurrentMultiViewModel}" />
</Grid>
OneViewModel.cs:
namespace Test.ViewModel
{
public class OneViewModel : ViewModelBase
{
public OneViewModel()
{
Messenger.Default.Register<Message>(this,"Token", FromMultiModel);
}
private void FromMultiModel(Message input)
{
MessageBox.Show(input.LoadingIndication);
}
}
}
OneView.cs
namespace Test.Views
{
public partial class OneView : UserControl
{
public OneView()
{
DataContext = new OneViewModel();
InitializeComponent();
}
}
}
App.xaml:
<DataTemplate DataType="{x:Type vm:OneViewModel}">
<views:OneView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:FourViewModel}">
<views:FourView/>
</DataTemplate>
You cannot create OneViewModel in OneView's ctor and assing it to datacontext.
The OneViewModel instance is created in BinaryMultiViewModel and when you set it to ContentControl using databinding and DataTempate with OneView usercontrol is chosen, then OneViewModel is set automatically as datacontext of OneView
just remove the line DataContext = new OneViewModel(); and it should work.

TabItem based on ViewModel type

1 - I have the TabControl. Its items source is collection of Tabs with
different types. I need to have a different XAML for each type. How to form TabItem header and content depends on ViewModel type?
2 - What is the best solution to encapsulate the XAML for each type of ViewModel? Should I have one UserControl for each type or there is a better solution?
HumanTabViewModel and InvaderTabViewModel are children of BaseViewModel class.
<TabControl ItemsSource="{Binding Tabs}">
</TabControl>
class PanelViewModel : BaseViewModel
{
private readonly ObservableCollection<BaseViewModel> _tabs = new ObservableCollection<BaseViewModel>();
public ObservableCollection<BaseViewModel> Tabs
{
get { return _tabs; }
}
private void InitTabs()
{
// Fill Tabs collection with some logic
var tab1 = new HumanTabViewModel ();
_tabs.Add(tab1);
var tab2 = new InvaderTabViewModel ();
_tabs.Add(tab2);
}
}
With the use of DataTemplates you can define a different looks for your types :
A DataTemplate is used to give a logical entity (.cs) a visual representation , once you assign your logical object (in your case invader/human vm's) as a Content , the framework will traverse up the logical tree looking for a DataTemplate for your type.
if it does not find any , it would just show the "ToString()" of your type.
In your case you have 2 Contents the TabItem.Content , and Header where can be assigned a DataTemplate via HeaderTemplate.
HumanView and InvaderView are UserControls.
CS :
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Items.Add(new HumanViewModel());
Items.Add(new InvaderViewModel());
}
private ObservableCollection<BaseViewModel> items;
public ObservableCollection<BaseViewModel> Items
{
get
{
if (items == null)
items = new ObservableCollection<BaseViewModel>();
return items;
}
}
}
public class BaseViewModel : INotifyPropertyChanged
{
public virtual string Header
{
get { return "BaseViewModel"; }
}
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
public class HumanViewModel : BaseViewModel
{
public override string Header
{
get
{
return "HumanViewModel";
}
}
}
public class InvaderViewModel : BaseViewModel
{
public override string Header
{
get
{
return "InvaderViewModel";
}
}
}
XAML :
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type local:HumanViewModel}">
<local:HumanView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:InvaderViewModel}">
<local:InvaderView />
</DataTemplate>
<Style TargetType="TabItem">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding Header,Mode=OneWay}" FontSize="18" FontWeight="Bold" Foreground="DarkBlue" Width="Auto"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{Binding Items, Mode=OneWay}" />
</Grid>
</Window>

Categories

Resources