I thought this would be pretty simple to do but seems I must be missing something blinding obvious.
The problem is that I am passing values to my UserControl (BoxPanel) but the values are not displayed. The blue box is displayed without text.
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<l:BoxPanel Number="1" Text="Hi" />
</Grid>
</Window>
BoxPanel.xaml
<UserControl x:Class="WpfApplication1.BoxPanel"
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"
Height="50" Width="90">
<Border Background="Blue">
<StackPanel>
<TextBlock FontSize="20" HorizontalAlignment="Center"
Text="{Binding Number}" />
<Label FontSize="10" HorizontalAlignment="Center" Foreground="White"
Content="{Binding Text}" />
</StackPanel>
</Border>
BoxPanel.xaml.xs
public partial class BoxPanel : UserControl
{
public static readonly DependencyProperty NumberProperty =
DependencyProperty.Register("Number", typeof(decimal), typeof(BoxPanel));
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(BoxPanel));
public BoxPanel()
{
InitializeComponent();
}
public decimal Number
{
get { return (decimal)GetValue(NumberProperty); }
set { SetValue(NumberProperty, value); }
}
public string Text
{
get { return (string)base.GetValue(TextProperty); }
set { base.SetValue(TextProperty, value); }
}
}
Binding paths, by default, are rooted at the DataContext. But you wish to bind to properties defined on the UserControl. So you have to redirect them somehow. I usually just do it by ElementName.
<UserControl x:Class="WpfApplication1.BoxPanel"
x:Name="BoxPanelRoot"
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"
Height="50" Width="90">
<Border Background="Blue">
<StackPanel>
<TextBlock Text="{Binding Number, ElementName=BoxPanelRoot}" />
<Label Content="{Binding Text, ElementName=BoxPanelRoot}" />
</StackPanel>
</Border>
It seems a little odd at first, and somewhat annoying to redirect bindings like this, but it is preferrable than other methods which utilize the DataContext within the UserControl. If you block the DataContext by, say, setting it to the root of the UserControl, you have effectively blocked the best method of passing data into the UserControl.
Rule of thumb, when binding in a UserControl, leave the DataContext alone unless you are explicitly binding against data passed to the UserControl.
Related
In my project, I wanted a UserControl that can display different formats of an image (bitmap, svg), selected from a ListBox. The SelectedItem of the ListBox is bound to the appropriate view model, which in turn changes the DataContext of the UserControl, and what I want to achieve is for it to change the displaying control (an Image for bitmaps, a SharpVectors.SvgViewBox for svg files) through data templates. It does so, but raises data binding errors, as if the templates were still intact whilst the UserControl's DataContext has already been changed.
I should like to a) avoid any data binding errors even if they cause no visible problems b) understand what is happening, so I prepared a MWE, which, to my surprise, displays the same behaviour, so I can present it here.
My minimal UserControl is as follows:
<UserControl x:Class="BindingDataTemplateMWE.VersatileControl"
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:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate DataType="{x:Type system:String}">
<TextBlock Text="{Binding Content.Length, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContentControl}}" />
</DataTemplate>
<DataTemplate DataType="{x:Type system:DateTime}">
<TextBlock Text="{Binding Content.DayOfWeek, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContentControl}}" />
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentControl
Content="{Binding .}" />
</Grid>
</UserControl>
The MainWindow that references this UserControl has the following XAML:
<Window x:Class="BindingDataTemplateMWE.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:BindingDataTemplateMWE"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox
Grid.Column="0"
SelectedItem="{Binding Selected}"
ItemsSource="{Binding Items}" />
<local:VersatileControl
Grid.Column="1"
DataContext="{Binding Selected}" />
</Grid>
</Window>
with the following code-behind (to make the MWE indeed minimal, I made the window its own DataContext, but originally there is a dedicated view model):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace BindingDataTemplateMWE
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private object selected;
public event PropertyChangedEventHandler PropertyChanged;
public List<object> Items { get; }
public object Selected {
get { return selected; }
set {
selected = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Selected)));
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
Items = new List<object>() { "a string", DateTime.Now, "another string" };
}
}
}
When I select a different item in the list, the desired effect takes place: the UserControl displays the length if a string is selected and the day of week when a DateTime. Still, I get the following binding error when selecting a DateTime after a string:
Length property not found on object of type DateTime.
and conversely, selecting a string after a DateTime yields
DayOfWeek property not found on object of type String.
It is clear that what I am doing is not meant to be done, but I do not know what the correct paradigm is and what happens in the background. Please advise me. Thank you.
I've seen this problem often when creating complex data templates (several levels of nesting) when views are loaded/unloaded. Honestly, some of such errors I am ignoring completely.
In your case something similar happens because you are manipulating DataContext directly. At the moment the new value is set, the previous value is still used in bindings, which monitor for source change and will try to update the target.
In your scenario you don't need this constant monitoring, so an easy fix is to use BindingMode.OneTime:
<DataTemplate DataType="{x:Type system:String}">
<TextBlock Text="{Binding Content.Length, RelativeSource={RelativeSource AncestorType=ContentControl}, Mode=OneTime}" />
</DataTemplate>
Edit:
How is the proper way to create a Control to avoid the following problem with the ElementName Binding:
<TextBox x:Name="MyTextBox" Text="some Text"></TextBox>
<Label>
<!--Binding works-->
<TextBlock Text="{Binding Path=Text, ElementName=MyTextBox, FallbackValue='Binding Failed'}"></TextBlock>
</Label>
<Button>
<!--Binding works-->
<TextBlock Text="{Binding Path=Text, ElementName=MyTextBox, FallbackValue='Binding Failed'}"></TextBlock>
</Button>
<local:MyUserControl>
<!-- THIS BINDING FAILS !!!-->
<TextBlock Text="{Binding Path=Text, ElementName=MyTextBox, FallbackValue='Binding Failed'}"></TextBlock>
</local:MyUserControl>
MyUserControl.xaml:
<UserControl x:Class="Problem.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" />
MyUserControl.xaml.cs
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
}
Original:
Im still new to WPF and could not find out how to do this properly.
I basically want a UserControl's child to retain the behavior of being able to bind to the root element's x:Name in a XAML.
This is an example that shows the problem caused by my UserControl Descriptor compared to the WPF Controls:
<Parent x:Name="_thisParent">
...
<Label>
<!-- Binding to _thisParent works -->
<TextBlock Text="{Binding Path=MyText, ElementName=_thisParent}" />
</Label>
<uc:Descriptor Text="description: ">
<!-- Binding to _thisParent FAILS !! -->
<TextBlock Text="{Binding Path=MyText, ElementName=_thisParent}" />
</uc:Descriptor>
<Button>
<!-- Binding to _thisParent works -->
<TextBlock Text="{Binding Path=MyText, ElementName=_thisParent}" />
</Button>
Here is the Code for my UserControl:
Descriptor.xaml
<UserControl
x:Class="EmbedContent.UserControls.Descriptor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="_thisDescriptor">
<UserControl.Template>
<ControlTemplate>
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="{Binding Path=Text, ElementName=_thisDescriptor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, FallbackValue='Binding Failed'}" />
<ContentPresenter Content="{Binding Path=Content, ElementName=_thisDescriptor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, FallbackValue='Binding Failed'}" />
</DockPanel>
</ControlTemplate>
</UserControl.Template>
Descriptor.xaml.cs
public partial class Descriptor : UserControl
{
#region Ctor
public Descriptor()
{
InitializeComponent();
}
#endregion
#region Dependency-Properties
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(Descriptor), new PropertyMetadata("Descriptor's default Text"));
#endregion
How would i need to implement a custom UserControl / ContentControl (if needed without xaml) to retain the behavior of the WPF Controls?
How is this done according to best practice anyway? I assume i only run into this problem because i'm doing something wrong.
Answer, including clarifying your decision: I finally found a solution.
Think about the problem a little deeper.
Binding a property of an element is part of the functionality of the element itself, not of its container.
Therefore, if you change the DataContext of an element, the default bindings are interpreted relative to it, not the DataContext of its container.
For this reason, you should not assign the DataContext inside a UserControl, as the default bindings behavior will change in a way that is unexpected for the user (programmer).
Now think about how ElementName bindings work.
For example, in the XAML that creates a new class from UserControl, you have defined named elements.
And then create multiple instances of UserControl.
In this case, if all elements with the same names were in the same visual tree, this would create a conflict, since it is not known which of them is being accessed.
(In this case, the "visual tree" is an oversimplification. It actually has to do with the system for registering the names of the UI elements).
To avoid such conflicts, when using XAML for ANY element (not only UserControl), its own local visual tree is created that is not associated with the main visual tree.
BUT!
I already wrote above, Bindings are part of the functionality of the element itself.
And therefore, the binding of a property of type ElementName will search for an element by name not higher in the container, but in the UserControl itself.
The same goes for Templates.
Therefore, the names of elements inside Templates are not visible outside of it.
Default elements are not implemented as UserControl, but as Custom Control.
In this case, there is a separate Sharp class with element logic.
And a separate default Template for this class.
In this implementation, since no XAML is used to create the element, the element does not have its own internal local visual tree.
And all its bindings (of the ElementName type) are interpreted in relation to the general visual tree.
To some extent, you partially implemented this in the Descriptor: ContentControl class.
Only instead of regregistering the default Template for the type, you set it up in the XAML App.
This implementation will work for an example, but in many cases it can create other problems.
Therefore, it is better to use the "standard" Custom Control for which the theme is created and the default Template is registered.
In the case of DescriptorTwo, you specify x: Class = "EmbedContent.UserControls.DescriptorTwo" and this automatically creates its own local visual tree, relative to which the ElementName bindings will work.
Slightly modified example for the solution you found.
using System.Windows;
using System.Windows.Controls;
namespace EmbedContent.CustomControls
{
public class Descriptor : ContentControl
{
#region Registering a default template
static Descriptor()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Descriptor), new FrameworkPropertyMetadata(typeof(Descriptor)));
}
#endregion
#region Ctor
public Descriptor() : base() { }
#endregion
#region Dependency-Properties
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(nameof(Text), typeof(string), typeof(Descriptor), new PropertyMetadata("Descriptor's default Text"));
#endregion
}
}
Theme with default templates - file Themes/Generic.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:customcontrols="clr-namespace:EmbedContent.CustomControls">
<Style TargetType="{x:Type customcontrols:Descriptor}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type customcontrols:Descriptor}">
<StackPanel>
<TextBlock x:Name="tblock" Text="Example Text in UserControl"/>
<TextBlock Text="{TemplateBinding Text}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
DescriptorTwo.xml:
<UserControl x:Class="EmbedContent.UserControls.DescriptorTwo"
x:Name="PART_Main"
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:EmbedContent.UserControls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<TextBlock x:Name="tblock" Text="Example Text in UserControl"/>
<TextBlock Text="{Binding Text, ElementName=PART_Main}"/>
</StackPanel>
</UserControl>
Examle Window:
<Window x:Class="EmbedContent.ExampleWindow"
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:EmbedContent"
xmlns:customcontrols="clr-namespace:EmbedContent.CustomControls"
xmlns:usercontrols="clr-namespace:EmbedContent.UserControls"
mc:Ignorable="d"
Title="ExampleWindow" Height="450" Width="800">
<StackPanel>
<TextBlock x:Name="tblock" Text="Example Text in Parent UIElement"/>
<Border Background="LightBlue" Margin="20" Padding="10"
BorderBrush="Blue" BorderThickness="2">
<customcontrols:Descriptor Text="{Binding Text, ElementName=tblock}"/>
</Border>
<Border Background="LightGreen" Margin="20" Padding="10"
BorderBrush="Green" BorderThickness="2">
<usercontrols:DescriptorTwo Text="{Binding Text, ElementName=tblock}"/>
</Border>
</StackPanel>
</Window>
Result:
As you can see in the XAML Designer and when launched at runtime, such bindings (in the UserControl) also work differently, which causes additional problems.
Therefore, the main purpose of the UserControl is to represent the Data coming through the DataContext.
Also consider the moment with the assignment of Content.
In UserControl in XAML, you set a value for this property.
And assigning it when used does not complement the presentation, but replace it.
In CustonControl, you can explicitly specify in the Template where to insert additional content.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:customcontrols="clr-namespace:EmbedContent.CustomControls">
<Style TargetType="{x:Type customcontrols:Descriptor}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type customcontrols:Descriptor}">
<StackPanel>
<TextBlock x:Name="tblock" Text="Example Text in UserControl"/>
<TextBlock Text="{TemplateBinding Text}"/>
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
<Window x:Class="EmbedContent.ExampleWindow"
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:EmbedContent"
xmlns:customcontrols="clr-namespace:EmbedContent.CustomControls"
xmlns:usercontrols="clr-namespace:EmbedContent.UserControls"
mc:Ignorable="d"
Title="ExampleWindow" Height="250" Width="400">
<StackPanel>
<TextBlock x:Name="tblock" Text="Example Text in Parent UIElement"/>
<Border Background="LightBlue" Margin="20" Padding="10"
BorderBrush="Blue" BorderThickness="2">
<customcontrols:Descriptor Text="{Binding Text, ElementName=tblock}">
<TextBlock Text="{Binding Text, ElementName=tblock}"/>
</customcontrols:Descriptor>
</Border>
<Border Background="LightGreen" Margin="20" Padding="10"
BorderBrush="Green" BorderThickness="2">
<usercontrols:DescriptorTwo Text="{Binding Text, ElementName=tblock}">
<TextBlock Text="{Binding Text, ElementName=tblock}"/>
</usercontrols:DescriptorTwo>
</Border>
</StackPanel>
</Window>
Result:
I finally found a solution but it still seems somewhat ugly so i expect someone to post a better solution.
I post the code here including an example highlighting the problem again.
Solution:
split UserControl / ContentControl up into a .cs class inheriting from e.g. ContentControl and set the Template describing the extra elements in the App.xaml Resources. Basically move the .xaml part as style into Resources.
Descriptor.cs
namespace EmbedContent.UserControls
{
class Descriptor : ContentControl{
#region Ctor
public Descriptor() : base() {}
#endregion
#region Dependency-Properties
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(Descriptor), new PropertyMetadata("Descriptor's default Text"));
#endregion
}
}
App.xaml
<Application
x:Class="EmbedContent.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:EmbedContent"
xmlns:uc="clr-namespace:EmbedContent.UserControls"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Style TargetType="uc:Descriptor">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="uc:Descriptor">
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="{Binding Path=Text, RelativeSource={RelativeSource AncestorType=uc:Descriptor}}" />
<ContentPresenter />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
The above can call and consume the Descriptor as i expect it from a WPF Control.
Here the Code for the UserControl, that breaks the binding when used:
DescriptorTwo.xaml
<UserControl
x:Class="EmbedContent.UserControls.DescriptorTwo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="_thisDescriptorTwo">
<UserControl.Template>
<ControlTemplate>
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="{Binding Path=Text, ElementName=_thisDescriptorTwo}" />
<ContentPresenter Content="{Binding Path=Content, ElementName=_thisDescriptorTwo}" />
</DockPanel>
</ControlTemplate>
</UserControl.Template>
DescriptorTwo.xaml.cs
namespace EmbedContent.UserControls
{
public partial class DescriptorTwo : UserControl
{
#region Ctor
public DescriptorTwo()
{
InitializeComponent();
}
#endregion
#region Dependency-Properties
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(DescriptorTwo), new PropertyMetadata("Descriptor's default Text"));
#endregion
}
}
And here an example of how both are called from another UserControl
Example.xaml
<UserControl
x:Class="EmbedContent.UserControls.EmbedContentExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:EmbedContent.UserControls"
x:Name="parent">
<StackPanel>
<Label>
<!-- Binding to parent works -->
<TextBlock Text="{Binding Path=MyText, ElementName=parent}" />
</Label>
<uc:Descriptor Text="description: ">
<!-- Binding to parent works -->
<TextBlock Text="{Binding Path=MyText, ElementName=parent}" />
</uc:Descriptor>
<uc:DescriptorTwo Text="description: ">
<!-- Binding to parent fails -->
<TextBlock Text="{Binding Path=MyText, ElementName=parent}" />
</uc:DescriptorTwo>
</StackPanel>
and the codebehind
Example.xaml.cs
namespace EmbedContent.UserControls
{
public partial class EmbedContentExample : UserControl
{
public EmbedContentExample()
{
InitializeComponent();
}
public string MyText
{
get { return (string)GetValue(MyTextProperty); }
set { SetValue(MyTextProperty, value); }
}
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register("MyText", typeof(string), typeof(EmbedContentExample), new PropertyMetadata("EmbedContentExample's MyText"));
}
}
Evidently using "Resources" to set an control's DataContext does not do what I think. I'm trying to stick close to MVVM. The following is an experiment in setting DataContext.
The MainWindow has a TabControl with two tabs, each displaying my pet's name, initally "Sam". Clicking the "ChangeName" button on Tab 1 changes the pet's name (to "Daisy") as expected. It does not change on Tab 2.
The content of Tab 2 is a Page, with its own DataContext, SecondTabViewModel. So I need to adjust the DataContext in the TextBlock in order to get at MyPet's name. This compiles ok, and Intellisense brings up the right things, so somehow within the control is being set. But the pet's name does not change.
Does the "StaticResource" generate instantiate a new copy of MainWindow or something? Can someone help me out? I'd love to know why this doesn't work, and what would work. This strategy for setting local DataContext is supposed to work according to the docs at https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/?view=netdesktop-5.0 but I must be misreading.
To abbreviate I've omitted some of the code (the pet class. But everything seems to be ok there, in I'm able to change the name on the first tab The Pet class implements INotifyPropertyChanged, I'm using the right handler etc.)
MainWindow.xmal
<Window x:Class="WpfApp9.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:WpfApp9"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<TabControl>
<TabItem Header="First Tab" Height="50">
<StackPanel>
<TextBlock Text="{Binding MyPet.Name}"/>
<Button Content="Change Name"
Command="{Binding ChangePetNameCommand}"/>
</StackPanel>
</TabItem>
<TabItem Header="Second Tab" Height="50">
<Frame Source="SecondTab.xaml"/>
</TabItem>
</TabControl>
</Grid>
</Window>
MainWindowViewModel
public class MainWindowViewModel
{
public Pet MyPet { get; set; }
public ICommand ChangePetNameCommand { get; set; }
public MainWindowViewModel()
{
MyPet = new Pet();
ChangePetNameCommand =
new RelayCommand(ChangePetName, (Object o) => true);
}
public void ChangePetName(object o)
{
MyPet.Name = "Daisy";
}
}
SecondTab.xmal
<Page x:Class="WpfApp9.SecondTab"
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:WpfApp9"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Title="SecondTab">
<Page.DataContext>
<local:SecondTabViewModel/>
</Page.DataContext>
<Page.Resources>
<local:MainWindowViewModel x:Key="M"/>
</Page.Resources>
<Grid>
<StackPanel>
<TextBlock Text="{Binding Source={StaticResource M},
Path = MyPet.Name}"/>
</StackPanel>
</Grid>
</Page>
SecondTabviewModel
namespace WpfApp9
{
public class SecondTabViewModel
{
public SecondTabViewModel()
{
}
}
}
The lines
<Page.Resources>
<local:MainWindowViewModel x:Key="M"/>
</Page.Resources>
in SecondTab.xaml are creating a second MainWindowViewModel instance.
In other words, SecondTab does not operate on the original MainWindowViewModel.
You would somehow have to pass a reference to the original MainWindowViewModel instance to SecondTabViewModel.
Instead of using a Frame and a Page, SecondTab could perhaps be a UserControl that simply inherits the DataContext from its parent element, and you could pass a view model object like
<TabItem Header="Second Tab" Height="50">
<local:SecondTab DataContext="{Binding SecondTabVM}"/>
</TabItem>
where SecondTabVM is a property of MainWindowViewModel that holds a SecondTabViewModel instance.
Learning C#, specifically WPF, and the MVVM framework. I'm creating a basic project that presents a MainWindow with a contentcontrol binding. Straightforward.
I have 2 views, each with a textbox. I have 2 buttons on the MainWindow, each allow me to toggle between views. However, when I enter data in a textbox, switch views, and come back, the data is gone. How can I persist that data to be consumed later?
Relevant code:
MainWindow.xaml
<Window x:Class="TestDataRetention.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:TestDataRetention"
xmlns:views="clr-namespace:TestDataRetention.Views"
xmlns:viewmodels="clr-namespace:TestDataRetention.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type viewmodels:View1ViewModel}">
<views:View1View DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:View2ViewModel}">
<views:View2View DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="60"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Right">
<Button x:Name="View1Button" Margin="10" Width="80" Content="View1" Click="View1Button_Click"/>
<Button x:Name="View2Button" Margin="10" Width="80" Content="View2" Click="View2Button_Click"/>
</StackPanel>
<ContentControl x:Name="Content" Grid.Row="0" Content="{Binding}"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using TestDataRetention.ViewModels;
namespace TestDataRetention
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void View1Button_Click(object sender, RoutedEventArgs e)
{
DataContext = new View1ViewModel();
}
private void View2Button_Click(object sender, RoutedEventArgs e)
{
DataContext = new View2ViewModel();
}
}
}
View1View.xaml
<UserControl x:Class="TestDataRetention.Views.View1View"
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:TestDataRetention.Views"
xmlns:vm="clr-namespace:TestDataRetention.ViewModels"
mc:Ignorable="d"
FontSize="24"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<vm:View1ViewModel/>
</UserControl.DataContext>
<Grid Background="AliceBlue">
<StackPanel>
<Label HorizontalAlignment="Center" Content="Enter View1 Stuff"/>
<TextBox x:Name="View1TextBox" Width="400" Height="50" Text="{Binding View1Words}" />
</StackPanel>
</Grid>
</UserControl>
View1View.xaml.cs
using System.Windows.Controls;
using TestDataRetention.ViewModels;
namespace TestDataRetention.Views
{
public partial class View1View : UserControl
{
public View1View()
{
InitializeComponent();
this.DataContext = new View1ViewModel();
}
}
}
View2 is obviously the same as View1 but with corresponding variables.
While there might also be a way to just have wpf cache it for you, you can just as easily save it properly and then have the input available at will.
Look here for two methods on how to:
How to save user inputed value in TextBox? (WPF, XAML)
At quick glance, you create new ViewModel each time your button is clicked, this will always create new ViewModel for your DataContext not using the original one.
Also this snippet from your code will create new ViewModel for your Control's DataContext regardless of the one the parent control has:
<UserControl.DataContext>
<vm:View1ViewModel/>
</UserControl.DataContext>
And I am not sure how you use your DataTemplate here:
<DataTemplate DataType="{x:Type viewmodels:View1ViewModel}">
<views:View1View DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:View2ViewModel}">
<views:View2View DataContext="{Binding}"/>
</DataTemplate>
But generally, as a guidance for you to model your MVVM project, always keep in mind that XAML code is translated to C# while compiling. So when writing something like <vm:View1ViewModel/> you actually do new View1ViewModel().
So for you to use the DataContext your control inherited from its parent, you just use <UserControl DataContext="{binding}" for your UserControl.
And for your button click, you have to keep a pointer for your previously created ViewModel and assign it to the DataContext when needed, I suggest you to create these ViewModels only when needed to minimize memory consumption in large applications, like:
private View1ViewModel m_View1VM = null;
private void View1Button_Click(object sender, RoutedEventArgs e)
{
if (m_View1VM is null)
m_View1VM = new View1ViewModel();
this.DataContext = m_View1VM;
}
I have a custom control inside a StackPanel
<Window x:Class="Video_Editor.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"
xmlns:m="clr-namespace:Video_Editor">
<Grid>
</StackPanel>
<ScrollViewer Margin="10,40,10,10" Grid.Row="2" VerticalScrollBarVisibility="Auto">
<StackPanel Name="stackPanel" >
<m:CustomControl Name="testControl"/>
</StackPanel>
</ScrollViewer>
</Grid>
The custom control currently doesn't do anything.
public class CustomControl: ItemsControl
{
}
I tried to do this in the window's constructor:
public MainWindow()
{
InitializeComponent();
testControl.Items.Add("item");
}
I am getting an error "the name "testControl does not exist in the current context.
You will have to use x:Name since you are deriving from another FrameworkElement that is exposing the Name property itself.
If a FrameworkElement has set its Name property (which ItemsControl seems to be doing) you cannot declare the Name property on the derived type, but you can use the Xaml x:Name property so you can decare a Name and access from code behind
Example:
<StackPanel Name="stackPanel" >
<m:CustomControl x:Name="testControl"/>
</StackPanel>