ContentControl with DataTemplate is not displaying anything (WPF MVVM) - c#

In my MainView, there is a Frame containing a ContentControl supposed to show a View depending on a ViewModel set in MainViewModel.
However, nothing shows on my MainView. Any idea why?
MainView
<Grid>
<Frame HorizontalAlignment="Center">
<Frame.Content>
<ContentControl Content="{Binding TestViewContext}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:TestViewModel}">
<local:TestView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Frame.Content>
</Frame>
</Grid>
MainViewModel
public class MainViewModel : BaseViewModel
{
private TestViewModel _testViewContext;
public TestViewModel TestViewContext
{
get { return _testViewContext; }
set { _testViewContext = value; OnPropertyChanged(nameof(TestViewContext)); }
}
public MainViewModel()
{
TestViewContext = new TestViewModel();
}
}
TestView
Just a red colored Page
TestViewModel
public class TestViewModel : ViewModelBase
{}

Frame is a bit special. Normally, child controls inherit the DataContext of their parent. However, with a Frame, the children do not inherit the DataContext. As a result, your ContentControl has a DataContext of null.
To verify this, give your ContentControl a name like the following:
<ContentControl x:Name="MyContentControl" Content="{Binding TestViewContext}">
Then in the constructor of your MainView, check the DataContext as follows:
public MainView()
{
// Other code
// Set a breakpoint here and view the DataContext
var dataContext = MyContentControl.DataContext;
}
For further reading, you could read the following post:
page.DataContext not inherited from parent Frame?
Also, as a side note, Frame intended use was setting the Source property to an external file. As you may have noticed, in order to set child content in xaml, you need to specify <Frame.Content> unlike other controls.

Related

How can I bind two user controls together?

I'm stuck in a problem right now and I think I'm lost.
My project (C# WPF MVVM) consists of the following components (no framework):
ShellView (Window)
ShellViewModel
MainView (Usercontrol) <-- 2 Textboxes for Userinputs (int values)
MainViewModel <-- Added both integer and put the result in a int-property
ResultDisplay (UserControl) <-- One Label witch should display the result
Both user controls are loaded into the shell view and displayed there.
How can I now connect the ResultDisply with the property of the MainView.
I've tried setting the Data.Context of the DisplayView to the MainViewModel and binding its label to the MainViewModel's property (ResultOutput).
When setting the autoproperty (1234) it works and 1234 is displayed. But everything that changes during runtime is no longer displayed.
I've already thought about DependensyProperty, but I can't implement it.
MainViewModel:
`
private string resultOutput = "1234";
public string ResultOutput
{
get { return resultOutput; }
set
{
resultOutput = value;
OnPropertyChanged("Result");
}
}
private void AddTwoNumbers()
{
int result = Num1 + num2;
ResultOutput = result.ToString();
}
DisplayView:
<UserControl.DataContext>
<viewModel:MainViewModel />
</UserControl.DataContext>
<Grid>
<Label x:Name="ResultTB"
Content="{Binding ResultOutput}"
Background="#333"
Foreground="LightGray"
FontSize="40"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Bottom"
Padding="8,0,0,5" />
</Grid>
</UserControl>
`
I hope someone can help me out of the darkness. :-)
<UserControl.DataContext>
<viewModel:MainViewModel />
</UserControl.DataContext>
This is wrong approach, because you create new instance of MainViewModel.
I've had the same problem, solution (without DependencyProperty) that works for me is to place your UserControl inside container (for example StackPanel) and bind DataContext of this container.
class ShellViewModel
{
public MainViewModel MainViewModel { get; }
public ShellViewModel()
{
MainViewModel = new MainViewModel();
}
}
<Window x:Class="ShellView" ...>
<StackPanel DataContext="{Binding MainViewModel}">
<MainView/>
<ResultDisplay/>
</StackPanel>
</Window>
So now MainView and ResultDisplay have the same ViewModel object

Handling Events in MVVM

So this is my first MVVM application. I have a "shell" view model named MainWindowViewModel for the main window that basically splits the view into two pages: MainWindowRibbon and MainWindowFrame. The MainWindowViewModel stores both pages as properties, which I plan to use databinding to update in the UI. Here is some of the code for reference:
MainWindowView xaml~
<Grid>
<Frame Content="{Binding MainWindowRibbon}" Grid.Column="0" Grid.Row="0"/>
<ScrollViewer>
<Frame Content="{Binding MainWindowFrame}"/>
</ScrollViewer>
</Grid>
MainWindowView code behind~
public partial class MainWindowView : Window
{
public MainWindowView()
{
InitializeComponent();
mainWindowViewModel = new MainWindowViewModel();
DataContext = mainWindowViewModel;
}
public MainWindowViewModel mainWindowViewModel;
}
MainWindowViewModel code~
public MainWindowViewModel()
{
//MainWindowRibbon and MainWindowFrame are declared as public Page properties
MainWindowRibbon = new MainWindowRibbonView();
MainWindowFrame = new WelcomePageView();
}
The MainWindowRibbonView, like the MainWindowView, instantiates the MainWindowRibbonViewModel.
My trouble comes when I wish to use an event within the MainWindowRibbonViewModel that will call for the MainWindowViewModel to reassign the MainWindowFrame page. I do not know how to connect the button command of the navigation bar I have created in the MainWindowRibbonView to cause an event or change in the MainWindowViewModel.
I do not know if the way I have organized this is ideal. Please let me know if I need to revise.
If somebody could help me determine the best approach, or even just a functioning one, I would be very grateful.
P.S.
Sorry if the naming conventions aren't the greatest.
Edit:
Lesson learned: listen to Joe.
I suppose it depends on what kind of button you are using in your navigation bar. is it a RadioButton? A RibbonToggleButton? Is it a regular button binding to an ICommand?
Since you called your Navigation Bar a "Ribbon", let us suppose it is a RibbonToggleButton (which is still basically a CheckBox). If it is checked, you show some view-model your "page 1". If it is not checked, you should another view-model representing your "page 2"
Let us also suppose your view's ribbon is up top. So you have two rows: the Ribbon row and the content row.
I might rewrite your MainWindow to look like this, (note the IsChecked property to some boolean property in your view-model like so:)
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- The "Ribbon" -->
<RowDefinition Height="*"/> <!-- The page content -->
</Grid.RowDefinitions>
<ToggleButton Content="Show Page 1" IsChecked="{Binding ShowPage1}"/>
<ScrollViewer Grid.Row=1>
<Frame Content="{Binding CurrentViewModel}"/>
</ScrollViewer>
</Grid>
And I might write your view-model like this: (Note that I assume it implements INotifyPropertyChanged and I call a function RaisePropertyChanged that I do not show.
public class Page1ViewModel {} // Fill this out with Page 1 properties
public class Page2ViewModel {} // Fill this out with Page 2 properties
// MainWindowViewModel. Implements INotifyPropertyChanged. Implementation
// is not shown here.
public class MainWindowViewModel : INotifyPropertyChanged
{
private Page1ViewModel = new Page1ViewModel();
private Page2ViewModel = new Page2ViewModel();
public MainWindowViewModel()
{
_currentViewModel = Page1ViewModel;
ShowPage1 = true;
}
private object _currentViewModel;
// The current contents of the main frame.
public object CurrentViewModel
{
get => _currentViewModel;
set
{
if (value == _currentViewModel)
return;
_currentViewModel = value;
RaisePropertyChanged();
}
// Should CurrentViewModel be page 1 or page 2?
public bool ShowPage1
{
get => return _currentViewModel == Page1ViewModel;
set
{
if (value == ShowPage1)
return;
CurrentViewModel = value ? Page1ViewModel : Page2ViewModel;
RaisePropertyChanged();
}
}
}
Note that I am not showing you any of the properties of Page1VieModel or Page2ViewModel nor have I shown you the implicit DataTemplates I assume you will write for them.
Also I am assuming that your navigation bar (and MainWindowView in general) have a DataContext that is already set to the MainWindowViewModel
The implemention with a command button or a RadioButton would be quite different.

RelayCommand not getting the right Model

I created a user control that looks like a tile. Created another user control named TilePanel that serves as the default container of the tiles. And lastly, the very UI that looks like a Window start screen. I used RelayCommand to bind my TileCommands
Here are the codes:
Tilev2.xaml
<UserControl x:Class="MyNamespace.Tilev2"
Name="Tile"....
>
...
<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" Command="{Binding ElementName=Tile, Path=TileClickCommand}" >
</Button>
</UserControl>
Tilev2.xaml.cs
public partial class Tilev2 : UserControl
{
public Tilev2()
{
InitializeComponent();
}
//other DPs here
public ICommand TileClickCommand
{
get { return (ICommand)GetValue(TileClickCommandProperty); }
set { SetValue(TileClickCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for TileClickCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TileClickCommandProperty =
DependencyProperty.Register("TileClickCommand", typeof(ICommand), typeof(Tilev2));
}
}
Then I created a TilePanel user control as the container of the tiles
TilePanel.xaml
<UserControl x:Class="MyNamespace.TilePanel"
...
>
<Grid>
<ScrollViewer>
<ItemsControl Name="tileGroup"
ItemsSource="{Binding TileModels}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local2:Tilev2 TileText="{Binding Text}"
TileIcon="{Binding Icon}"
TileSize="{Binding Size}"
TileFontSize="{Binding FontSize}"
Background="{Binding Background}"
TileCaption="{Binding TileCaption}"
TileCaptionFontSize="{Binding TileCaptionFontSize}"
TileClickCommand="{Binding TileCommand}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</UserControl>
TilePanel.xaml.cs
public partial class TilePanel : UserControl
{
public TilePanel()
{
InitializeComponent();
DataContext = new TilePanelViewModel();
}
public TilePanelViewModel ViewModel
{
get { return (TilePanelViewModel)this.DataContext; }
}
}
My ViewModel for TilePanel
TilePanelViewModel.cs
public class TilePanelViewModel : ViewModelBase
{
private ObservableCollection _tileModels;
public ObservableCollection<TileModel> TileModels
{
get
{
if (_tileModels == null)
_tileModels = new ObservableCollection<TileModel>();
return _tileModels;
}
}
}
Then my Tile model
TileModel.cs
public class TileModel : BaseNotifyPropertyChanged
{
//other members here
ICommand tileCommand { get; set; }
//other properties here
public ICommand TileCommand
{
get { return tileCommand; }
set { tileCommand = value; NotifyPropertyChanged("TileCommand"); }
}
}
}
This is my StartScreen View where TilePanels with tiles should be displayed...
StartScreen.xaml
<UserControl x:Class="MyNamespace.StartMenu"
... >
<Grid>
<DockPanel x:Name="dockPanel1" Grid.Column="0" Grid.Row="1" Margin="50,5,2,5">
<local:TilePanel x:Name="tilePanel"></local:TilePanel>
</DockPanel>
</Grid>
</UserControl>
StartScreen.xaml.cs
public partial class WincollectStartMenu : UserControl, IView<StartMenuViewModel>
{
public WincollectStartMenu()
{
InitializeComponent();
}
public StartMenuViewModel ViewModel { get { return (DataContext as StartMenuViewModel); } }
private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ViewModel.Tile = tilePanel.ViewModel.TileModels;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
return;
}
}
In my start screen ViewModel, I used ObservableCollection Tile
and use Tile.Add(tile); to populate my start screen with Tiles inside the TilePanel...
StartMenuViewModel.cs
TileModel tile = new TileModel() { Text = "Testing1", FontSize = 11, Size = TileSize.Medium, Background = (SolidColorBrush)new BrushConverter().ConvertFromString("#039BE5"), Tag="Something" };
tile.TileCommand = new RelayCommand(
p => Tile_TileClick(tile.Tag),
p => true
);
temp.Add(tile);
Now the problem is, if I add a new code below, tile = new TileModel() {...}
tile.TileCommand = new RelayCommand(...), even if I clicked on the first tile, my Tile_TileClick() will get the second tile's info (or the last tile inserted)...
Am I doing something wrong? Or Im doing everything wrong...?
This is not direct answer to your question, but hopefully it will give you few thoughts.
Ok, first of all, don't name your usercontrol like this:
<UserControl x:Class="MyNamespace.Tilev2" Name="Tile"/>
because the name can be easily overriden when using the usercontrol somewhere:
<local:Titlev2 Name="SomeOtherName" />
and the binding inside Tilevs with ElementName won't work: Command="{Binding ElementName=Tile, Path=TileClickCommand}"
Second, what's the point of Tilev2 usercontrol? Why don't just put the button directly to the DataTemplate inside TilePanel class?
If you need to reuse the template, you can put the template to resource dictionary.
If you need some special presentation code in the Tilev2 codebehind or you need to use the Tilev2 without viewmodel, it's better to create custom control instead of usercontrol in this case. it has much better design time support, and writing control templates it's easier (Triggers, DataTriggers, TempalteBinding, etc). If you used custom Control insead UserControl, you wouldn't have to write {Binding ElementName=Tile, Path=TileClickCommand}, or use RelativeSource, etc.
Third, it seems like you forced MVVM pattern where you can't really take advantage of it. Point of MVVM is separate application logic from presentation. But your Tile and TilePanel usercontrols are just presentation. You application logic could be in StartScreen which is concrete usage of TileName.
I would create custom controls called TilePanel (potentionally inherited from ItemsControl, Selector or ListBox) and if needed also for Tile. Both controls should not be aware of any viewmodels. There's absolutelly no need for that.
Take ListBox as an example. ListBox does not have viewmodel but can be easily used in MVVM scenarios. Just because ListBox it is not tied to any viewmodel, it can be databound to anything.
Just like ListBox creates ListBoxItems, or
Combobox creates ComboBoxItems, or
DataGrid creates DataGridRows or
GridView (in WinRT) creates GridViewRow, your TilePanel could create Tiles.
Bindings to tile specific properties, like Icon or Command could be specified in TilePanel.ItemContainerStyle orusing simillar appriach like DisplayMemberPath, resp ValueMemberPath in ListBox.
final usage could the look like:
<TilePanel ItemsSource="{Bidning ApplicationTiles}" />
or
<TilePanel>
<Tile Icon=".." Command=".." Text=".." />
<Tile Icon=".." Command=".." Text=".." />
</TilePanel>
Last, the name `TilePanel' evoked that it is some kind of panel like StackPanel, WrapPanel, etc. In other words, it is FrameworkElement inherited from Panel.
TilesView would be more suitable name for the control than TilePanel. The -View postfix is not from MVVM, it just follows naming convention -GridView, ListView...
Saw the problem...
To pass a parameter from button, I used CommandParameter so I could use it in switch-case scenario to know which button was clicked. But still, param was still null...
<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" CommandParameter="{Binding}" Command="{Binding Path=TileClickCommand, ElementName=Tile}" >
</Button>
TileCommand = new MyCommand() { CanExecuteFunc = param => CanExecuteCommand(), ExecuteFunc = param => Tile_TileClick(param)}
After 2 whole damn days, I changed it:
From this:
<UserControl Name="Tile"...>
<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" CommandParameter="{Binding Tag, ElementName=Tile}" Command="{Binding Path=TileClickCommand, ElementName=Tile}" >
</Button>
</UserControl>
To this:
<UserControl Name="Tile"...>
<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" CommandParameter="{Binding}" Command="{Binding Path=TileClickCommand, ElementName=Tile}" >
</Button>
</UserControl>
My first post does error because CommandParameter does not know where to get its DataContext so I replaced it to CommandParameter={Binding} so it will get whatever from the DataContext.

Avalon Dock 2.0 LayoutItemTemplateSelector given ContentPresenter instead of ViewModel

I've been at this for weeks...I am creating a WPF application that uses Avalon Dock 2.0 in the the Main Window. I am trying to use the Docking Manager in a MVVM way, so I have DockingManager.DocumentsSource bound to an ObservableCollection<object> property in my MainViewModel. I also created a custom DataTemplateSelector and bound it to DockingManager.LayoutItemTemplateSelector. The problem I am having:
I add a ViewModel to the documents source.
My custom DataTemplateSelector.SelectTemplate() is called.
The item parameter in SelectTemplate() is a System.Windows.Controls.ContentPresenter instead of the ViewModel object that I added.
Even if I return the correct DataTemplate, it ends up getting bound to the ContentPresenter instead of the ViewModel contained within the ContentPresenter.
I managed to replicate the problem in a bare-bones WPF project, here is the relevant code:
MainWindow:
<!-- MainWindow markup DataContext is bound to
I omitted the usual xmlns declarations -->
<Window
xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock"
xmlns:local="clr-namespace:AvalonTest"
Title="MainWindow">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<xcad:DockingManager DocumentsSource="{Binding Docs}">
<xcad:DockingManager.LayoutItemTemplateSelector>
<local:TestTemplateSelector>
<local:TestTemplateSelector.TheTemplate>
<DataTemplate>
<local:TestView/>
</DataTemplate>
</local:TestTemplateSelector.TheTemplate>
</local:TestTemplateSelector>
</xcad:DockingManager.LayoutItemTemplateSelector>
<xcad:LayoutRoot>
<xcad:LayoutPanel Orientation="Vertical">
<xcad:LayoutAnchorablePane/>
<xcad:LayoutDocumentPane/>
</xcad:LayoutPanel>
</xcad:LayoutRoot>
</xcad:DockingManager>
</Grid>
</Window>
MainViewModel:
class MainViewModel
{
//Bound to DockingManager.DocumentsSource
public ObservableCollection<object> Docs { get; private set; }
public MainViewModel()
{
Docs = new ObservableCollection<object>();
Docs.Add(new TestViewModel());
}
}
DataTemplateSelector:
class TestTemplateSelector : DataTemplateSelector
{
public TestTemplateSelector() {}
public DataTemplate TheTemplate { get; set; }
//When this method is called, item is always a ContentPresenter
//ContentPresenter.Content will contain the ViewModel I add
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
//Just return the only template no matter what
return TheTemplate;
}
}
TestView:
<!-- TestTemplateSelector will always return this TestView -->
<UserControl x:Class="AvalonTest.TestView"
xmlns:local="clr-namespace:AvalonTest">
<Grid>
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding TestText}"/>
<Button Content="A Button"/>
</StackPanel>
</Grid>
</UserControl>
TestViewModel:
//TestView.DataContext should be set to this, but instead
//it gets set to a containing ContentPresenter
class TestViewModel : ObservableObject
{
private string testText = "TESTTESTTEST";
public string TestText
{
get { return testText; }
set
{
testText = value;
RaisePropertyChanged("TestText");
}
}
}
The Result:
TestView is not properly bound to the TestViewModel and therefore "TESTTESTTEST" does not show up in the TextBox. I have checked out Avalon Dock's sample MVVM project and their DataTemplateSelector always gets the ViewModel instead of ContentPresenter. What am I doing wrong?
Change the definition for SelectTemplate on TestTemplateSelector as follows:
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
//check if the item is an instance of TestViewModel
if (item is TestViewModel)
return TheTemplate;
//delegate the call to base class
return base.SelectTemplate(item, container);
}
You should always check if the item passed is an instance of your target view model and if isn't, delegate the call to the base class so WPF can handle the objects you don't care about.

Binding ViewModel to ContentControl as its DataContext

I want to change UserControls on button clicks (I'm not going to complicate here, so I'll only mention important parts). So idea was to bind ViewModels of those UserControls to ContentControl, and than associate them Views using DataTemplates.
Here's the code:
<Window x:Class="Project.MainWindow">
<Window.Resources>
<DataTemplate DataType="{x:Type UserControl:ViewUserControlViewModel}" >
<UserControl:ViewUserControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type UserControl:EditUserControlViewModel}" >
<UserControl:EditUserControl/>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl DataContext="{Binding UserControlViewModel}" />
<Button Content="View" Click="ChangeToView()"/>
<Button Content="Edit" Click="ChangeToEdit()"/>
</Grid>
</Window>
ViewModel:
public class MainWindowViewModel : DependencyObject
{
public DependencyObject UserControlViewModel
{
get { return (DependencyObject)GetValue(UserControlViewModelProperty); }
set { SetValue(UserControlViewModelProperty, value); }
}
public static readonly DependencyProperty UserControlViewModelProperty =
DependencyProperty.Register("UserControlViewModel", typeof(DependencyObject), typeof(MainWindowViewModel), new PropertyMetadata());
public MainWindowViewModel()
{
UserControlViewModel = new EditUserControlViewModel();
}
}
But theres a problem. When I start project, I only see buttons but not any UserControls. What did I do wrong?
If your Window.DataContext is properly set to MainWindowViewModel this should do the job
<ContentControl Content="{Binding UserControlViewModel}" />
When doing mvvm your viewmodel should implement INotifyPropertyChanged and not inherit from DependencyObject.
public class MainWindowViewModel : INotifyPropertyChanged
{
private object _currentWorkspace; //instead of object type you can use a base class or interface
public object CurrentWorkspace
{
get { return this._currentWorkspace; }
set { this._currentWorkspace = value; OnPropertyChanged("CurrentWorkspace"); }
}
public MainWindowViewModel()
{
CurrentWorkspace= new EditUserControlViewModel();
}
//todo: to switch the workspace, create DelegeCommand/RelayCommand and set the CurrentWorkspace
//if you don't know about these commands let me know and i post it
public ICommand SwitchToViewCommand {get{...}}
public ICommand SwitchToEditCommand {get{...}}
}
xaml: you should set the Content Property to your CurrentWorkspace.
<ContentPresenter Content="{Binding UserControlViewModel}" />
<Button Content="View" Comamnd="{Binding SwitchToViewCommand}"/>
<Button Content="Edit" Comamnd="{Binding SwitchToEditCommand}"/>
! Don't forget to set the DataContext for your window to your MainWindowViewModel instance.
First of all you should post the code of your UserControl since (in your code snippet above) it's responsible for displaying some data.
Second you are not binding anything in your code.
Third your implementation of the ViewModel is wrong. You don't need to subclass a DependencyObject but instead implement the INotifyPropertyChanged interface in order to establish a ViewModel that is capable of notifying your View.
Fourth I don't know what you are doing with
<ContentControl DataContext="{Binding UserControlViewModel}" />
maybe you can explain further ?
Fifth when implementing the MVVM patterm (what you currently not do) you should avoid using events like the click event and instead use Commands.
(I know that's not a real answer yet, but I don't wanted to write in comment syntax)

Categories