Currently I've got a single window and several user controls in a WPF application. My objective is to load them in the XAML via a binding to a property in the View Model. I've been looking for a way to do this for some time now, but most solutions hinge on using the code-behind instead of the View Model. Is there anyway to do this via a binding? Something like:
<StackPanel Height="500" HorizontalAlignment="Left" Margin="0,46,0,0" Name="stackPanel1" VerticalAlignment="Top" Width="500" Content="{Binding SomeUserControl}" />
And in the code:
private Control _someUserControl;
public Control SomeUserControl
{
get { return _someUserControl; }
set { _someUserControl = value; }
}
You can use a ContentPresenter instead of the StackPanel, since there is only a single element:
<ContentPresenter Height="500" HorizontalAlignment="Left" Margin="0,46,0,0" VerticalAlignment="Top" Width="500" Content="{Binding SomeUserControl}" />
Related
I added in my code copying the first TabItem along with all the controls. But now how to reference to these controls to make the appropriate modifications (I mean change label text)? Do I must need make new class and binding ?
I want clone tab item to store data in labels about for example other apps.
XAML code:
<TabControl x:Name="MainTabControl" x:FieldModifier="public">
<TabItem x:Name="TabItem1" x:FieldModifier="public" Header="Tab 1">
<Grid>
<Label x:Name="Label1" Content="Test 1" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" FontSize="18"/>
<Label x:Name="Label2" Content="Test 2" HorizontalAlignment="Left" Margin="11,44,0,0" VerticalAlignment="Top" FontSize="18"/>
<Button x:Name="Button1" Content="Show Open Dialog" IsEnabled="True" HorizontalAlignment="Left" Margin="11,185,0,0" VerticalAlignment="Top" Width="194" Click="Button1_Click"/>
</Grid>
</TabItem>
C# code:
public MainWindow()
{
InitializeComponent();
mainWindow = this;
TabItem tab2 = TrycloneElement(TabItem1);
if (tab2 != null) MainTabControl.Items.Add(tab2);
}
public static T TrycloneElement<T>(T orig)
{
try
{
string s = XamlWriter.Save(orig);
StringReader stringReader = new StringReader(s);
XmlReader xmlReader = XmlTextReader.Create(stringReader, new XmlReaderSettings());
XmlReaderSettings sx = new XmlReaderSettings();
object x = XamlReader.Load(xmlReader);
return (T)x;
}
catch
{
return (T)((object)null);
}
}
You can access the controls by walking the visual tree using Content, Children or other properties.
TabItem tab2 = TrycloneElement(TabItem1);
var grid = (Grid) tab2.Content;
var label1 = (Label) grid.Children[0];
var label2 = (Label) grid.Children[1];
var button = (Button) grid.Children[2];
An alternative is to use the VisualTreeHelper and convenience helper methods like described here:
How can I find WPF controls by name or type?
Now, these methods work, but you do not need them. You are actually trying to reinvent the wheel. Reusing the same visual structure with different data can be achieved with data templating in a convenient way.
Data Templating Overview
You can use the same technique for your TabControl. I will show you a quick conceptual example that would even comply with the MVVM pattern. This example is not complete. It serves as a base for you to learn about an alternative to your current approach. You can of course also make this approach work in code-behind with a few modifications.
Create a data class like below with your application information. Here I assume that it is not editable, otherwise you need to implement INotifyPropertyChanged in order to update the user interface on changes.
public class AppInfo
{
public AppInfo(string name, string description)
{
Name = name;
Description = description;
}
public string Name { get; }
public string Description { get; }
}
Expose a list of AppInfo (or an ObservableCollection if the collection is changed at runtime).
public List<AppInfo> AppInfos { get; }
Initialize the list appropriately and add your items, for example:
AppInfos = new List<AppInfo>
{
new AppInfo("Visual Studio", "A popular IDE."),
new AppInfo("Calculator", "Does the math for you."),
new AppInfo("Chrome", "A web browser.")
};
The tab control would bind its ItemsSource to the AppInfos.
<TabControl ItemsSource="{Binding AppInfos}">
In order to create a reusable visual representation, create a DataTemplate as ItemTemplate (this is the tab header) and a ContentTemplate (this is the tab content).
<TabControl ItemsSource="{Binding AppInfos}">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:AppInfo}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:AppInfo}">
<Grid>
<Label Content="{Binding Name}"
HorizontalAlignment="Left"
Margin="10,10,0,0"
VerticalAlignment="Top"
FontSize="18"/>
<Label Content="{Binding Description}"
HorizontalAlignment="Left"
Margin="11,44,0,0"
VerticalAlignment="Top"
FontSize="18"/>
<Button Content="Show Open Dialog"
IsEnabled="True"
HorizontalAlignment="Left"
Margin="11,185,0,0"
VerticalAlignment="Top" Width="194"
Command="{Binding DataContext.OpenCommand, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}"
CommandParameter="{Binding}"/>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Now, both templates are reused for each item in the AppInfos collection. The data template uses bindings to the corresponding properties, which automatically pick up the values for each item.
The Button is special here, since it can be clicked to execute an action. This action is probably the same for all items, except that it needs a concrete item to work with. You can encapsulate this logic in commands.
MVVM - Commands, RelayCommands and EventToCommand
This is another important aspect that can make your life easier, but is beyond the scope of this question. There are lots of tutorials out there. You could expose another property:
public ICommand OpenCommand{ get; }
The button in XAML binds its Command to this property (RelativeSource is needed to get the right data context for binding). The CommandParameter will pass the current AppInfo item to the command. In code, you could then execute your custom logic on this item. No need to know about user interface elements, only data.
Disclaimer: I know that this is pretty overwhelming. That is why I provided an immediate (dirty) solution to your issue and an example with links to resources to gradually learn a better way with concrete samples for your exact problem. Once you get used to MVVM, data binding and data templating, you will see how much easier more complex issues can be solved and what the benefits are in terms of maintainability and reuseability.
In WPF I have a window that includes a user control. The window and user control each have a view model. I want to pass a parameter from the window's VM to the UC's VM. After a fair amount of looking, I haven't found a way.
The window XAML sets its data context to its VM. The UC includes a custom dependency property for the parameter. I want to use SetBinding to bind the DP to the UC VM.
If I set the UC data context to its VM, then the parameter binding doesn't work. If I don't set the UC data context then the parameter binding works but the UC VM is not referenced.
How can I pass a parameter AND bind to the UC VM?
UC XAML
<UserControl x:Name="userControl" x:Class="Test_Paramaterized_UserControl_with_MVVM.UserControl1"
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:Test_Paramaterized_UserControl_with_MVVM"
xmlns:view="clr-namespace:Daavlin.SmartTouch.STUV_WPF.View"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Margin="10">
<Border BorderThickness="3" BorderBrush="Black" Padding="10">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<TextBlock Text="UserControl1 View: "/>
<TextBlock Text="{Binding ElementName=userControl, Path=PropUserControlView, Mode=OneWay}" FontWeight="Bold"/>
</StackPanel>
<Rectangle Height="5"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="UserControl1 ViewModel: " />
<TextBlock Text="{Binding PropUserControlViewModel, FallbackValue=propUserControlViewModel 2}" FontWeight="Bold">
<TextBlock.DataContext>
<local:UserControl1ViewModel/>
</TextBlock.DataContext>
</TextBlock>
</StackPanel>
</StackPanel>
</Border>
</Grid>
</UserControl>
UC code-behind & VM
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public string PropUserControlView { get => (string)GetValue(PropUserControlViewProperty); set => SetValue(PropUserControlViewProperty, value); }
public static readonly DependencyProperty PropUserControlViewProperty =
DependencyProperty.Register(nameof(PropUserControlView), typeof(string), typeof(UserControl1),
new PropertyMetadata(null, DependencyPropertyChanged));
private static void DependencyPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var x = dependencyPropertyChangedEventArgs.NewValue;
}
}
public class UserControl1ViewModel : ObservableObject
{
public string PropUserControlViewModel { get => _propUserControlViewModel; set => SetField(ref _propUserControlViewModel, value); }
private string _propUserControlViewModel = "value from UserControl-ViewModel";
}
Window XAML
<Window x:Class="Test_Paramaterized_UserControl_with_MVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test_Paramaterized_UserControl_with_MVVM"
Title="MainWindow" >
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid VerticalAlignment="Top" >
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Margin="20">
<StackPanel Orientation="Horizontal">
<TextBlock Text="MainWindow1 ViewModel: "/>
<TextBox Text="{Binding PropWindowViewModel, UpdateSourceTrigger=PropertyChanged}" FontWeight="Bold"/>
</StackPanel>
<Rectangle Height="10"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="UserControl1 (fixed value Fixed): " VerticalAlignment="Center"/>
<local:UserControl1 PropUserControlView="Fixed"/>
</StackPanel>
<Rectangle Height="10"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="UserControl1 (bound to MainWindows VM): " VerticalAlignment="Center"/>
<local:UserControl1 PropUserControlView="{Binding PropWindowViewModel}"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
Window code-behind & VM
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class MainWindowViewModel : ObservableObject
{
public string PropWindowViewModel { get => _propWindowViewModel; set => SetField(ref _propWindowViewModel, value); }
private string _propWindowViewModel = "valuefrom Window-VIewModel";
}
As far as I understood, what you meant was :-
1) You have a user control which has its own view model.
2) You have a Window where you have its own view model.
You want to link both and pass parameters from your WindowViewModel to UserControlViewModel.
What you can do is, Keep a property (e.g. UCViewModel) of type UserControlViewModel in your WindowViewModel and set the datacontext of the user control in your XAML to
<local:UserControl1 DataContext="{Binding UCViewModel}" .../>
Now that you can access anything that is there in your UserControlViewModel via WindowViewModel, you can set any property value OR pass any parameter to your UserControlViewModel from WindowViewModel.
If you need a code reference, let me know. We have been using user controls in a similar way and it works fine.
I want to use SetBinding to bind the DP to the UC VM.
Is that really a requirement? SetBinding() requires that the target property be a dependency property, which in turn requires that the target object be a dependency object. Your view model object is not a dependency object, and of course none of its properties are dependency properties.
Achieving that goal would require a much bigger change to your code than is otherwise apparently necessary.
If I set the UC data context to its VM, then the parameter binding doesn't work
Why not? You didn't show code that attempts this, so it's difficult to understand what you mean here. It's not a good idea to have the user control set its own DataContext anyway. That property is public, and you don't want to expose your implementation details to client code. Doing so invites bugs where the client code has set the DataContext to the wrong thing, disabling everything in your UserControl.
But that said, if by "parameter binding" you mean the binding in the MainWindow XAML, assigning {Binding PropWindowViewModel} to the PropUserControlView property of the user control, then just setting the DataContext of the user control should not affect that. You still have the dependency property in the user control, and anything bound that within the user control should still work.
Finally, it's not entirely clear why you want the dependency property tied to the view model. In the user control's XAML, you can (as you've already done) bind directly to the user control's dependency property. There's no need for a property in the view model to replicate that.
Maybe you have code in the view model somewhere else that wants to respond to changes in this value? It's not clear, and it's difficult to give the best advice without knowing the whole story.
All that said, the code you posted above can be made to work with a couple of small changes. First, you'll need to expose the TextBlock where you've created the view model, so that the user control code-behind has access to it:
<TextBlock x:Name="textBlock1" Text="{Binding PropUserControlViewModel, FallbackValue=propUserControlViewModel 2}" FontWeight="Bold">
<TextBlock.DataContext>
<l:UserControl1ViewModel/>
</TextBlock.DataContext>
</TextBlock>
I.e. add the x:Name="textBlock1" to the declaration.
Then, you need to use the property-change notification for your dependency property to update the view model property any time the dependency property changes:
private static void DependencyPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
UserControl1 uc = (UserControl1)dependencyObject;
UserControl1ViewModel vm = (UserControl1ViewModel)uc.textBlock1.DataContext;
vm.PropUserControlViewModel = (string)dependencyPropertyChangedEventArgs.NewValue;
}
The above works in your limited example, but you'll probably want to give the DependencyPropertyChanged() method a more descriptive name, specific to the actual property in question.
If you do choose to mirror the dependency property in the view model this way, IMHO a better way to do that would be to set the user control's root element (i.e. the Grid) so that its data context is your view model, and then throughout the rest of the XAML, bind only to the view model. Mixing the view model and dependency property is not wrong per se, but it does introduce an inconsistency that can make it harder to test and maintain the code.
So, I made a really simple attempt to try out data binding from a property of a class that I have, but, for whatever reason, the code actually do anything. It's not throwing any errors, but something must not be working right. I'm just currently testing if it'll behave like I want it to, which, in this case, will set the opacity of a rectangle to zero. Here's the xaml for the Data Template that doesn't seem to want to respond correctly:
<HubSection x:Name="China" Width="440" Height="460" Background="#FF343434" Header="China" IsHeaderInteractive="True" Tapped="{x:Bind HubSectionTapped}" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="50,0,0,0">
<DataTemplate x:DataType="data:MainPageView">
<Grid Height="460" Width="410" VerticalAlignment="Bottom" x:Name="ChinaBackground">
<Image Source="Assets/chinaFlag.bmp" x:Name="ChinaFlag"/>
<Rectangle x:Name="ChinaSelected_Rect" Width="410" Height="30" VerticalAlignment="Bottom" Fill="BlueViolet" Opacity="{x:Bind Opacity1}"/>
</Grid>
</DataTemplate>
</HubSection>
And here's the code behind:
public MainPageView TheMainPageView;
public MainPage()
{
this.InitializeComponent();
timer = new DispatcherTimer();
timer.Tick += Timer_DyanmicResize;
timer.Tick += Timer_SelectionIndicator;
timer.Start();
TheMainPageView = new MainPageView ();
}
And finally, here's the class MainPageView that's referenced:
public class MainPageView
{
public int Opacity1 {get; set;}
public int Opacity2 {get;set;}
public int Opacity3 { get; set; }
public MainPageView()
{
this.Opacity1 = 0;
this.Opacity2 = 0;
this.Opacity3 = 0;
}
}
In the XAML I included the xmlns:data="using:TestApp.Models" (models is the folder in which the class MainPageView is housed). As I said, it's not throwing errors, but it's not doing anything either, so I'm a bit at a loss of where to start addressing this because there aren't any errors to trace back. Thanks in advance for any help you guys can provide
HubSection uses a DataTemplate to define the content for the section, content can be defined inline, or bound to a data source. When using binding in this DataTemplate, we need set DataContext property of HubSection to provide data source for the DataTemplate.
{x:Bind} does not use the DataContext as a default source—instead, it uses the page or user control itself. So it will look in the code-behind of your page or user control for properties, fields, and methods.
This is right when you use {x:Bind} directly in page or user control. While Inside a DataTemplate, there is a little difference.
Inside a DataTemplate (whether used as an item template, a content template, or a header template), the value of Path is not interpreted in the context of the page, but in the context of the data object being templated. So that its bindings can be validated (and efficient code generated for them) at compile-time, a DataTemplate needs to declare the type of its data object using x:DataType.
For more information about Data binding in UWP, please check Data binding in depth.
To fix your issue, you just need to set DataContext in HubSection like following:
<HubSection x:Name="China" Width="440" Height="460" Background="#FF343434" Header="China" IsHeaderInteractive="True" Tapped="{x:Bind HubSectionTapped}" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="50,0,0,0" DataContext="{x:Bind TheMainPageView}">
<DataTemplate x:DataType="data:MainPageView">
<Grid Height="460" Width="410" VerticalAlignment="Bottom" x:Name="ChinaBackground">
<Image Source="Assets/chinaFlag.bmp" x:Name="ChinaFlag"/>
<Rectangle x:Name="ChinaSelected_Rect" Width="410" Height="30" VerticalAlignment="Bottom" Fill="BlueViolet" Opacity="{x:Bind Opacity1}"/>
</Grid>
</DataTemplate>
</HubSection>
Here when using {x:Bind} in HubSection, it uses the page itself as its data source as HubSection is in the page directly. So it can get TheMainPageView field in the code-behind. But for the {x:Bind} in DataTemplate, it can't as
its data source is the data object being templated not the page. So we need to provide this data object by setting DataContext property of HubSection.
Check you output window for errors but I imagine you might see a binding error in there. Opacity is a double, you are using an int so will get a type conversion error.
Following situation: I got a base class which provides a little framework for making modal dialogs with an adorner.
The base class has a property of type DataTemplate which contains the actual input scheme (all kinds of input are possible) as well as an object property which contains the mapping model (a model class to which the template binds it's input values).
Because I want to reuse the adorner, I made it have a ContentControl which anon has a ContentTemplate with the actual dialog design. The dialog's design contains a ContentControl whose Template is bound to the property in the adorner class. The DataContext of the adorner's ContentControl is set to itself, of course.
Now the embedded ContentControl (in the design) generates the DataTemplate and displays (in the current case) a TextBox. This TextBox now should be bound to the model. Therefore I reused the DataContext of the adorner design template for the actual input template. Here's how I've done it:
The adorner's ControlTemplate
<Border Grid.Row="0" Background="{DynamicResource InputAdornerHeaderBackground}" BorderThickness="0,0,0,1" CornerRadius="0">
<TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Stretch" TextWrapping="Wrap" Text="{Binding Header}"
FontSize="{DynamicResource InputAdornerHeaderFontSize}" Foreground="{DynamicResource InputAdornerHeaderForeground}"
FontWeight="{DynamicResource InputAdornerHeaderFontWeight}" Margin="8" />
</Border>
<Border Grid.Row="1" BorderThickness="1,0,1,1" BorderBrush="{DynamicResource InputAdornerBorderBrush}" CornerRadius="0">
<ContentControl ContentTemplate="{Binding InputControlTemplate}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</Border>
</Grid>
</ContentTemplate>
The actual input template (DataTemplate)
<DataTemplate x:Key="TextInputTemplate">
<Grid Background="Black" DataContext="{Binding DataContext.InputMapping, RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}}">
<TextBox Text="{Binding Path=Text, Mode=OneWayToSource}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="0"
adorners:InputAdorner.FocusElement="{Binding RelativeSource={RelativeSource Self}}" />
</Grid>
</DataTemplate>
Model class
public sealed class TextInputModel
{
public string Text { get; set; }
}
Adorner properties
public DataTemplate InputControlTemplate
{
get { return _inputControlTemplate; }
private set
{
if (Equals(value, _inputControlTemplate)) return;
_inputControlTemplate = value;
OnPropertyChanged();
}
}
public object InputMapping
{
get { return _inputMapping; }
private set
{
if (Equals(value, _inputMapping)) return;
_inputMapping = value;
OnPropertyChanged();
}
}
FYI: The model is being dynamically instantiated when the Adorner is being created. It does not get set twice. This must be some kind of binding issue.
The template shows correctly. I see and can input stuff into the textbox, but once I fetch the model all properties are default (""). It did work one or two times but somehow design changes have obviously made it disfunctional.
I don't get what is interfering here as from my point of view all should be set up correctly. I checked the context of the DataTemplate: It is the actual model class. Yet the textbox inputs do not update the property.
EDIT:
For some reason it seems that the attatched property is causing this issue. But why is it interfering? It does not override the DataContext, does it?
I'm trying to programatically create a button flyout, within my XAML I have:
<Page.Resources>
<Button x:Key="LaunchFlyout" Content="LAUNCH">
<Button.Flyout>
<Flyout Placement="Top">
<Grid Width="200" Height="200">
<StackPanel>
<Rectangle Fill="Red" Width="100" Height="100" />
<Rectangle Fill="Green" Width="100" Height="100" />
</StackPanel>
</Grid>
</Flyout>
</Button.Flyout>
</Button>
</Page.Resources>
Nested within grids I have:
<Grid x:Name="launchBtn_grid" Grid.Column="1">
</Grid>
And then in my code within the Page_Loaded method I have:
bool hasContainer = localSettings.Containers.ContainsKey("appStatus");
if (!hasContainer) {
Button button = (Button)this.Resources["LaunchFlyout"];
launchBtn_grid.Children.Add(button);
}
else {
Button button = new Button();
button.Content = "LAUNCH";
button.Click += launch_btn_Click;
launchBtn_grid.Children.Add(button);
}
When I debug this, it reaches the IF statement and reaches this line launchBtn_grid.Children.Add(button); and then I get this error Element is already the child of another element.
Does anyone understand why? I have already looked and they dont already exist so I don't understand why it is giving me this error. Does anyone know what I am doing wrong?
I'm not sure in what context/use case your are doing that, but it feels weird to me to have an actual control as a Resource (not a DataTemplate, Style, etc).
If you only want to have 1 button of the 2 different template, why not switch Visibility on the 2 instead of loading controls from your code behind ?
Going forward with the idea, just add both buttons in the Grid within your XAML and switch their Visibility according to the setting you read.
There is a BooleanToVisibilityConverter within the framework to help you with this.