wpf - can't use custom control - c#

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>

Related

Setting a datacontext via resources

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.

How do I persist the value of the Textbox Text property of a user control when switching from one view to another?

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;
}

How to get property from the DataTemplate's parent ViewModel class

I am using MVVVM Light, so I set the DataContext of the Page to the Locator. Then I set the Pivot's ItemSource to a collection property inside "myFirstVM" ViewModel class. But how to set the text of the header of the PivotItem which is in a dataTemplate of TextBox to "MyProperty" which is also defined in "myFirstVM" class?
I look at this example, but cannot figure it out:
How to access Parent's DataContext in Window 8 store apps
Here is my code:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:myApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ViewModel="using:myApp.ViewModel"
x:Class="myApp.MyTripsPage"
mc:Ignorable="d"
DataContext="{Binding Source={StaticResource Locator}}">
<Grid x:Name="LayoutRoot">
<Pivot Name="myPivot"
Tag="{Binding}"
ItemsSource="{Binding myFirstVM.DataSource}"
ItemTemplate="{Binding myFirstVM.ViewDataTemplate}">
<Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyProperty, ElementName=myPivot}"/>
</DataTemplate>
</Pivot.HeaderTemplate>
</Pivot>
</Grid>
using ElementName in a binding will bind to the element itself (Pivot in this case), whereas you want to bind to something in the DataContext of Pivot, so just add DataContext to your path:
<TextBlock Text="{Binding DataContext.myFirstVM.MyProperty, ElementName=myPivot}"/>

DelegateCommand inside Listview with UserControl

Is there any method to use my DelegateCommand inside my ListView with UserControl:
UserControl:
<UserControl
x:Class="App13.UserControls.ItemTemplateControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App13"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DataContext="{d:DesignInstance Type=local:MainViewModel, IsDesignTimeCreatable=True}"
mc:Ignorable="d">
<Grid>
<Button Content="Click" Command="{Binding OpenCommand"/>
</Grid>
</UserControl>
There is no error in my MainViewModel. There is error in Binding.
I can easily use OpenCommand in MainPage xaml using this code:
d:DataContext="{d:DesignInstance Type=local:MainViewModel, IsDesignTimeCreatable=True}"
<Button Content="Click" Command="{Binding OpenCommand"/>
How can I bind OpenCommnad to my UserControl?
Sorry for my English and thanks in advance!
This is my ListView:
<ListView x:Name="peopleListBox">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<UserControls:ItemTemplateControl/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the construction of your UserControl do
this.DataContext = new MainViewModel();
d:DataContext is just the Design time DataContext setting which is not applied at runtime.
By the name of it, DesignInstance is meant for design-time and not run-time.
In MVVM there are two approches of setting your ViewModel.
ViewFirst or ViewModelFirst - depending wether you build your app top down or bottom up.
for ViewFirst You can set your DataContext from your xaml :
<UserControl
x:Class="App13.UserControls.ItemTemplateControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App13"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<UserControl.DataContext>
<local:MainViewModel/>
</UserControl.DataContext>
<!-- Rest of your implementation ... -->
</UserControl>
For ViewModelFirst, set it in your code behind (usually done from View's constructor)
this.DataContext = new MainViewModel();
If you want to bind a property from your viewModel to an Item in your ListBox, bind your button inside the UserControl as follows:
<Button Content="Click"
Command="{Binding DataContext.OpenCommand,
RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
/>
Hope this helps

Why are my UserControl bindings not working?

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.

Categories