I am working on a WPF application and doing my best to follow the MVVM architecture. I am using the GalaSoft MVVM light relay command implementation for all my commands and behavior.
Currently, I am trying to learn and understand attached behaviors, and specifically, how to implement an attached behavior for the text blocks in my application.
What I would like to do is have a style that I can apply to select text blocks that would execute some universal command (more on what I mean by "universal" later)
Here is an example of what I want to do.
For the example, I have two windows. Obviously in the real application I will have many more, but this should suit my instructive needs.
I'd like to apply an attached behavior to the text blocks in these windows that will implement a defined behavior.... Code...
Main Window
<Window x:Class="AttachedExample.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"
DataContext="{Binding Source={StaticResource Locator}, Path=Main}">
<StackPanel>
<TextBlock Text="{Binding Path=SomeMainWindowModel.SomeText}" />
</StackPanel>
</Window>
Main Window View Model
public class MainWindowViewModel : BaseViewModel
{
private MainWindowModel _someMainWindowModel = new MainWindowModel();
public MainWindowModel SomeMainWindowModel
{
get
{
return this._someMainWindowModel;
}
set
{
this._someMainWindowModel = value;
this.NotifyPropertyChange("SomeMainWindowModel");
}
}
}
Main Window Model
public class MainWindowModel : BaseModel
{
private string _someText = "Some main text for Stack Overflow!";
public string SomeText
{
get
{
return this._someText;
}
set
{
this._someText = value;
this.NotifyPropertyChange("SomeText");
}
}
}
And now the secondary window....
Secondary Window
<Window x:Class="AttachedExample.SecondaryWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SecondaryWindow"
Height="300"
Width="300"
DataContext="{Binding Source={StaticResource Locator}, Path=Secondary}">
<StackPanel>
<TextBlock Text="{Binding Path=SomeSecondaryWindowModel.SomeSecondaryText}" />
</StackPanel>
</Window>
Secondary Window View Model
public class SecondaryWindowViewModel : BaseViewModel
{
private SecondaryWindowModel _someSecondaryWindowModel = new SecondaryWindowModel();
public SecondaryWindowModel SomeSecondaryWindowModel
{
get
{
return this._someSecondaryWindowModel;
}
set
{
this._someSecondaryWindowModel = value;
this.NotifyPropertyChange("SomeSecondaryWindowModel");
}
}
}
Secondary Window Model
public class SecondaryWindowModel : BaseModel
{
private string _someSecondaryText = "Some secondary text for Stack Overflow!";
public string SomeSecondaryText
{
get
{
return this._someSecondaryText;
}
set
{
this._someSecondaryText = value;
this.NotifyPropertyChange("SomeSecondaryText");
}
}
}
What I want to do is be able to have one style in a resource dictionary or in the App.xaml that I can apply to each of these text blocks. The style would specify an attached behavior that would execute a method with the argument of the content of the text block, right clicked.
Pseudo Code
*Right Click text block on MainWindow;*
SomeStaticClass.ExecuteSomeStaticCustomMethod(mainWindowTextBlock.Content.ToString());
*Right Click text block on SecondaryWindow;*
SomeStaticClass.ExecuteSomeStaticCustomMethod(secondaryWindowTextBlock.Content.ToString());
That's a whole lot of example code and explanation to describe something that could be accomplished with an event handler in the code behind... but that wouldn't be following the MVVM pattern.
Please remember I am using MVVM light in your replies.
THis might be what you are looking for and is MVVM compliant. I use a style to attach a relay command to the textblock event which is inside a usercontrol, this usercontrol binds to the MVVM_Light Viewmodel and has the datacontext.
<Style x:Key="StyleName" TargetType="{x:Type TextBlock}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBlock}">
<Border x:Name="Bd" SnapsToDevicePixels="true" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseRightButtonDown">
<cmd:EventToCommand Command="{Binding DataContext.PreviewMouseRightButtonDownCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding Name, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBox}}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I removed the extra style properties to make it easier to read. You need to create a command in your view model that is called PreviewMouseRightButtonDownCommand of type string. THen it will get the textblock name as the parameter. You can change the parameter to whatever binding you want. THis way whatever textblock I want to trigger this event command just needs to have its style set to this StyleName.
Hope this helps
Related
I have been unable to find a clean, simple, example of how to correctly implement a usercontrol with WPF that has a DependencyProperty within the MVVM framework. My code below fails whenever I assign the usercontrol a DataContext.
I am trying to:
Set the DependencyProperty from the calling ItemsControl , and
Make the value of that DependencyProperty available to the ViewModel of the called usercontrol.
I still have a lot to learn and sincerely appreciate any help.
This is the ItemsControl in the topmost usercontrol that is making the call to the InkStringView usercontrol with the DependencyProperty TextInControl (example from another question).
<ItemsControl ItemsSource="{Binding Strings}" x:Name="self" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="v:InkStringView">
<Setter Property="FontSize" Value="25"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the InkStringView usercontrol with the DependencyProperty.
XAML:
<UserControl x:Class="Nova5.UI.Views.Ink.InkStringView"
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"
x:Name="mainInkStringView"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding TextInControl, ElementName=mainInkStringView}" />
<TextBlock Grid.Row="1" Text="I am row 1" />
</Grid>
</UserControl>
Code-Behind file:
namespace Nova5.UI.Views.Ink
{
public partial class InkStringView : UserControl
{
public InkStringView()
{
InitializeComponent();
this.DataContext = new InkStringViewModel(); <--THIS PREVENTS CORRECT BINDING, WHAT
} --ELSE TO DO?????
public String TextInControl
{
get { return (String)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}
}
That is one of the many reasons you should never set the DataContext directly from the UserControl itself.
When you do so, you can no longer use any other DataContext with it because the UserControl's DataContext is hardcoded to an instance that only the UserControl has access to, which kind of defeats one of WPF's biggest advantages of having separate UI and data layers.
There are two main ways of using UserControls in WPF
A standalone UserControl that can be used anywhere without a specific DataContext being required.
This type of UserControl normally exposes DependencyProperties for any values it needs, and would be used like this:
<v:InkStringView TextInControl="{Binding SomeValue}" />
Typical examples I can think of would be anything generic such as a Calendar control or Popup control.
A UserControl that is meant to be used with a specific Model or ViewModel only.
These UserControls are far more common for me, and is probably what you are looking for in your case. An example of how I would use such a UserControl would be this:
<v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" />
Or more frequently, it would be used with an implicit DataTemplate. An implicit DataTemplate is a DataTemplate with a DataType and no Key, and WPF will automatically use this template anytime it wants to render an object of the specified type.
<Window.Resources>
<DataTemplate DataType="{x:Type m:InkStringViewModel}">
<v:InkStringView />
</DataTemplate>
<Window.Resources>
<!-- Binding to a single ViewModel -->
<ContentPresenter Content="{Binding MyInkStringViewModelProperty}" />
<!-- Binding to a collection of ViewModels -->
<ItemsControl ItemsSource="{Binding MyCollectionOfInkStringViewModels}" />
No ContentPresenter.ItemTemplate or ItemsControl.ItemTemplate is needed when using this method.
Don't mix these two methods up, it doesn't go well :)
But anyways, to explain your specific problem in a bit more detail
When you create your UserControl like this
<v:InkStringView TextInControl="{Binding text}" />
you are basically saying
var vw = new InkStringView()
vw.TextInControl = vw.DataContext.text;
vw.DataContext is not specified anywhere in the XAML, so it gets inherited from the parent item, which results in
vw.DataContext = Strings[x];
so your binding that sets TextInControl = vw.DataContext.text is valid and resolves just fine at runtime.
However when you run this in your UserControl constructor
this.DataContext = new InkStringViewModel();
the DataContext is set to a value, so no longer gets automatically inherited from the parent.
So now the code that gets run looks like this:
var vw = new InkStringView()
vw.DataContext = new InkStringViewModel();
vw.TextInControl = vw.DataContext.text;
and naturally, InkStringViewModel does not have a property called text, so the binding fails at runtime.
You're almost there. The problem is that you're creating a ViewModel for your UserControl. This is a smell.
UserControls should look and behave just like any other control, as viewed from the outside. You correctly have exposed properties on the control, and are binding inner controls to these properties. That's all correct.
Where you fail is trying to create a ViewModel for everything. So ditch that stupid InkStringViewModel and let whoever is using the control to bind their view model to it.
If you are tempted to ask "what about the logic in the view model? If I get rid of it I'll have to put code in the codebehind!" I answer, "is it business logic? That shouldn't be embedded in your UserControl anyhow. And MVVM != no codebehind. Use codebehind for your UI logic. It's where it belongs."
Seems like you are mixing the model of the parent view with the model of the UC.
Here is a sample that matches your code:
The MainViewModel:
using System.Collections.Generic;
namespace UCItemsControl
{
public class MyString
{
public string text { get; set; }
}
public class MainViewModel
{
public ObservableCollection<MyString> Strings { get; set; }
public MainViewModel()
{
Strings = new ObservableCollection<MyString>
{
new MyString{ text = "First" },
new MyString{ text = "Second" },
new MyString{ text = "Third" }
};
}
}
}
The MainWindow that uses it:
<Window x:Class="UCItemsControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:UCItemsControl"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<v:MainViewModel></v:MainViewModel>
</Window.DataContext>
<Grid>
<ItemsControl
ItemsSource="{Binding Strings}" x:Name="self" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="v:InkStringView">
<Setter Property="FontSize" Value="25"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<v:InkStringView TextInControl="{Binding text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Your UC (no set of DataContext):
public partial class InkStringView : UserControl
{
public InkStringView()
{
InitializeComponent();
}
public String TextInControl
{
get { return (String)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}
(Your XAML is OK)
With that I can obtain what I guess is the expected result, a list of values:
First
I am row 1
Second
I am row 1
Third
I am row 1
You need to do 2 things here (I'm assuming Strings is an ObservableCollection<string>).
1) Remove this.DataContext = new InkStringViewModel(); from the InkStringView constructor. The DataContext will be one element of the Strings ObservableCollection.
2) Change
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
to
<v:InkStringView TextInControl="{Binding }" />
The xaml you have is looking for a "Text" property on the ItemsControl to bind the value TextInControl to. The xaml I put using the DataContext (which happens to be a string) to bind TextInControl to. If Strings is actually an ObservableCollection with a string Property of SomeProperty that you want to bind to then change it to this instead.
<v:InkStringView TextInControl="{Binding SomeProperty}" />
I have built the following control:
MyContentControl.xaml.cs:
public partial class MyContentControl : UserControl
{
public MyContentControl()
{
InitializeComponent();
}
public static readonly DependencyProperty MyContentProperty = DependencyProperty.Register(
"MyContent", typeof(object), typeof(MyContentControl), new PropertyMetadata(default(object)));
public object MyContent
{
get { return (object) GetValue(MyContentProperty); }
set { SetValue(MyContentProperty, value); }
}
}
MyContentControl.xaml:
<UserControl x:Class="MyContentControl"
x:Name="self"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Border BorderBrush="Gray" BorderThickness="1">
<ContentPresenter Content="{Binding ElementName=self, Path=MyContent}"/>
</Border>
</Grid>
</UserControl>
And it can be used like this:
<controls:MyContentControl>
<controls:MyContentControl.MyContent>
<TextBox Text="Some text..."/>
</controls:MyContentControl.MyContent>
</controls:MyContentControl>
What I would like to achieve is being able to use my control like this:
<controls:MyContentControl>
<TextBox Text="Some text..."/>
</controls:MyContentControl>
I would like to define the inner content like I would e.g. for a StackPanel.
How can this be achieved?
You need to decorate the MyContentControl class with the ContentPropertyAttribute https://learn.microsoft.com/de-de/dotnet/api/system.windows.markup.contentpropertyattribute?view=net-5.0
[ContentProperty("MyContent")]
public partial class MyContentControl : UserControl
{
...
Then you should be able to directly add the content without explicitly specifying "<controls:MyContentControl.MyContent>" in Property Element syntax. So that the markup below should parse and be valid:
<controls:MyContentControl>
<TextBox Text="Some text..."/>
</controls:MyContentControl>
Although lidqy's answer was closest to what I had initially intended, I ended up following Andy's suggestion from the comments to the question.
The key to that approach is: the desired custom XAML is defined in a style/template, in any available resource dictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:MyControls">
<Style TargetType="{x:Type controls:MyContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:MyContentControl}">
<Grid>
<Border BorderBrush="Gray" BorderThickness="1">
<ContentPresenter/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
The MyContentControl class then only needs to inherit ContentControl - and does not need to be a UserControl (with .xaml and xaml.cs) files.
#Andy: Regarding the question of "named elements": the exception/error is "Error MC3093: Cannot set Name attribute value 'xxx' on element 'yyy'. 'yyy' is under the scope of element 'MyContentControl', which already had a name registered when it was defined in another scope.", pretty much like here.
#lidqy: I also thought "why not just use the Content property?", but when trying I apparently created some kind of recursive binding.
Thank you, both! :o)
NOTE: The above approach should work for all ContentControls, including UserControls, enabling re-templating of basically any custom control ;o)
I have a custom control (MediaPlayer) that contains 2 other custom controls, a media player (Host) and a control bar (UI).
This control in itself is quite simple, it just binds the two together for display.
Now the first problem I got is that I couldn't set Host or UI properties from MediaPlayer, so I duplicated all properties relevant at design-time and linked them via binding. Is this the right away of achieving this? It's kind of clunky but it works.
<Style TargetType="{x:Type local:MediaPlayerWpf}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MediaPlayerWpf}">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid x:Name="PART_HostGrid" Margin="0,0,0,46">
<!--Filled by SetPlayerHost -->
</Grid>
<local:PlayerControls x:Name="PART_MediaUI" Height="46" Width="Auto"
VerticalAlignment="Bottom" MouseFullscreen="{TemplateBinding MouseFullscreen}"
MousePause="{TemplateBinding MousePause}"
IsPlayPauseVisible="{Binding IsPlayPauseVisible, RelativeSource={RelativeSource TemplatedParent}}"
IsStopVisible="{TemplateBinding IsStopVisible}"
IsLoopVisible="{TemplateBinding IsLoopVisible}"
IsVolumeVisible="{TemplateBinding IsVolumeVisible}"
IsSpeedVisible="{TemplateBinding IsSpeedVisible}"
IsSeekBarVisible="{TemplateBinding IsSeekBarVisible}"
PositionDisplay="{TemplateBinding PositionDisplay}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This is a class for a generic media player. Then I have another Custom Control deriving from it that sets it to use a specific media player. (have one using MPV video player, and another one displaying a VapourSynth script output)
The derived class looks like this.
<Style TargetType="{x:Type local:VsMediaPlayer}" BasedOn="{StaticResource {x:Type ui:MediaPlayerWpf}}" />
Now the problem is I want to expose Script and Path properties as dependency properties so they can be databound. I can't take exactly the same approach as above, so how can I do it? The Host the Path and Script will be bound to is created at run-time within OnApplyTemplate.
I'm a bit confused about how to make this one work, and I'm not sure the first code above is the best solution. Thanks for any guidance.
I guess one option is to copy the base style template instead of inheriting from it, and I could initiate the Host class there instead of at run-time. Any other option?
Edit: Host property is declared like this in my generic MediaPlayer class, but I couldn't find a way to set its sub-properties (Host.Source) from the designer.
public static DependencyProperty HostProperty = DependencyProperty.Register("Host", typeof(PlayerBase), typeof(MediaPlayerWpf),
new PropertyMetadata(null, OnHostChanged));
public PlayerBase Host { get => (PlayerBase)GetValue(HostProperty); set => SetValue(HostProperty, value); }
private static void OnHostChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
MediaPlayerWpf P = d as MediaPlayerWpf;
if (e.OldValue != null)
P.HostGrid.Children.Remove(e.OldValue as PlayerBase);
if (e.NewValue != null) {
P.HostGrid.Children.Add(e.NewValue as PlayerBase);
P.TemplateUI.PlayerHost = e.NewValue as PlayerBase;
}
}
Edit: this is the XAML code of MediaPlayer
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:EmergenceGuardian.MediaPlayerUI">
<Style TargetType="{x:Type local:MediaPlayerWpf}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MediaPlayerWpf}">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<ContentPresenter x:Name="PART_HostGrid" Margin="0,0,0,46"
Content="{TemplateBinding Content}" />
<local:PlayerControls x:Name="PART_MediaUI" Height="46" Width="Auto"
VerticalAlignment="Bottom" MouseFullscreen="{TemplateBinding MouseFullscreen}"
MousePause="{TemplateBinding MousePause}"
IsPlayPauseVisible="{Binding IsPlayPauseVisible, RelativeSource={RelativeSource TemplatedParent}}"
IsStopVisible="{TemplateBinding IsStopVisible}"
IsLoopVisible="{TemplateBinding IsLoopVisible}"
IsVolumeVisible="{TemplateBinding IsVolumeVisible}"
IsSpeedVisible="{TemplateBinding IsSpeedVisible}"
IsSeekBarVisible="{TemplateBinding IsSeekBarVisible}"
PositionDisplay="{TemplateBinding PositionDisplay}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Adding x:FieldModifier="public" to PART_MediaUI throws "The attribute FieldModifier does not exist in namespace"
SOLUTION!!! After working with a few attached properties, I finally understand how they work, and attached properties are indeed the right solution. This will allow me to set UIProperties.IsVolumeVisible on the parent class. I just need to repeat that code for every property.
public static class UIProperties {
// IsVolumeVisible
public static readonly DependencyProperty IsVolumeVisibleProperty = DependencyProperty.RegisterAttached("IsVolumeVisible", typeof(bool),
typeof(UIProperties), new UIPropertyMetadata(false, OnIsVolumeVisibleChanged));
public static bool GetIsVolumeVisible(DependencyObject obj) => (bool)obj.GetValue(IsVolumeVisibleProperty);
public static void SetIsVolumeVisible(DependencyObject obj, bool value) => obj.SetValue(IsVolumeVisibleProperty, value);
private static void OnIsVolumeVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (!(d is MediaPlayerWpf P))
return;
P.UI.IsVolumeVisible = (bool)e.NewValue;
}
}
I found a partial solution. Instead of inheriting MediaPlayer from Control, I inherit from ContentControl.
In MediaPlayer Generic.xaml, I display the content like this right above the UI controls
<ContentPresenter x:Name="PART_HostGrid" Margin="0,0,0,46" Content="{TemplateBinding Content}" />
Override property metadata to ensure content is of type PlayerBase and to pass the content reference to the UI control
static MediaPlayerWpf() {
ContentProperty.OverrideMetadata(typeof(MediaPlayerWpf), new FrameworkPropertyMetadata(ContentChanged, CoerceContent));
}
public override void OnApplyTemplate() {
base.OnApplyTemplate();
UI = TemplateUI;
UI.PlayerHost = Content as PlayerBase;
}
private static void ContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
MediaPlayerWpf P = d as MediaPlayerWpf;
if (P.TemplateUI != null)
P.TemplateUI.PlayerHost = e.NewValue as PlayerBase;
}
private static object CoerceContent(DependencyObject d, object baseValue) {
return baseValue as PlayerBase;
}
And then I can use it like this
<MediaPlayerUI:MediaPlayerWpf x:Name="Player" IsVolumeVisible="False" IsSpeedVisible="False" IsLoopVisible="False" PositionDisplay="Seconds">
<VapourSynthUI:VsMediaPlayerHost x:Name="PlayerHost" />
</MediaPlayerUI:MediaPlayerWpf>
The advantage is that I no longer need to inherit from MediaPlayerWpf so there are less controls to manage.
However, I still need to duplicate UI properties to expose them to the designer, haven't found a way to access them in any other way.
Setting x:FieldModifier="public" in Generic.xaml results in "The attribute 'FieldModifier' does not exist in XML namespace"
UI is exposed as a dependency property like this. The designer allows to set UI="..." but not UI.IsVolumeVisible="false" nor recognizes < local:UI>. Is there a way to expose it from within a custom control?
public static DependencyPropertyKey UIPropertyKey = DependencyProperty.RegisterReadOnly("UI", typeof(PlayerControls), typeof(MediaPlayerWpf), new PropertyMetadata());
public static DependencyProperty UIProperty = UIPropertyKey.DependencyProperty;
public PlayerControls UI { get => (PlayerControls)GetValue(UIProperty); private set => SetValue(UIPropertyKey, value); }
I gave a comment above about how you could use a DependencyProperty and set it that type etc. This is all good but may be overkill for what you need. Just use the x:FieldModifier="public" to get what you're looking for.
Here's an example:
I make 3 user controls and have my MainWindow. The user controls are MainControl, SubControlA, SubControlB.
In MainControl I first give the controls a logical name and then FieldModifier to public.
<UserControl x:Class="Question_Answer_WPF_App.MainControl"
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:Question_Answer_WPF_App"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<local:SubControlA x:Name="SubControlA" x:FieldModifier="public"/>
<local:SubControlB x:Name="SubControlB" x:FieldModifier="public"/>
</StackPanel>
</UserControl>
Then I place that MainControl in my MainWindow and use it like so:
<Window x:Class="Question_Answer_WPF_App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Question_Answer_WPF_App"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<local:MainControl>
<local:SubControlA>
<TextBlock Text="I'm in SubControlA" />
</local:SubControlA>
</local:MainControl>
</Grid>
</Window>
Hope this helps. The idea is you can also reference the DependencyPropertys from those controls also like Visibility etc. (or whatever you were using in yours in the question.)
This is just an example as I wouldn't recommend doing it this cheap.
Ok, to follow up the answer from your comments / questions below let me explain how it works a little deeper. First, the SubControlA and SubControlB are just two empty UserControls that I made to have the example work.
In xaml anything between brackets gets initialized at that point. We use the namespace / type name to target the property and whatever is between the brackets goes to the setter of that property.
Consider this MainWindow... All I do is place a custom UserControl in it and it looks like this in xaml
<Window x:Class="Question_Answer_WPF_App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Question_Answer_WPF_App"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<local:ExampleControl />
</Window>
… and it looks like this in when ran
Now to see the custom ExampleControl because so far no big deal.
<UserControl x:Class="Question_Answer_WPF_App.ExampleControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:Media="clr-namespace:System.Windows.Media;assembly=PresentationCore"
xmlns:Windows="clr-namespace:System.Windows;assembly=PresentationCore"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<StackPanel>
<Button Visibility="Visible"
Height="50"
Background="Blue"
Content="Button A"/>
<Button>
<Button.Visibility>
<Windows:Visibility> Visible </Windows:Visibility>
</Button.Visibility>
<Button.Height>
<System:Double> 50 </System:Double>
</Button.Height>
<Button.Background>
<Media:SolidColorBrush>
<Media:SolidColorBrush.Color>
<Media:Color>
<Media:Color.R> 0 </Media:Color.R>
<Media:Color.G> 0 </Media:Color.G>
<Media:Color.B> 255 </Media:Color.B>
<Media:Color.A> 255 </Media:Color.A>
</Media:Color>
</Media:SolidColorBrush.Color>
</Media:SolidColorBrush>
</Button.Background>
<Button.Content> Button B </Button.Content>
</Button>
</StackPanel>
</UserControl>
In this ExampleControl I have two identical buttons, except one says Button A and the other Button B.
Notice how I referenced the properties in the first button via the properties name directly (which is mostly used) but I reference it by namespace / type for the second one. They have the same results...
Also notice that I had to include the reference to the namespace for certain types like:
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:Media="clr-namespace:System.Windows.Media;assembly=PresentationCore"
xmlns:Windows="clr-namespace:System.Windows;assembly=PresentationCore"
XAML has a built in parser that takes the string you send and attempts to designate initialize it as the type needed for the property. See how this works for enums (Visibility : System.Windows.Visibility), primitives (Height : System.Double), and unique objects like (Background : System.Windows.Media.Brush).
Also notice that Background being a Brush type can be of any type that derives from Brush. In the example I use a SolidColorBrush which has a base of Brush.
However; I also take it a step further in the Background. Notice that not only do I assign the SolidColorBrush but I assign the Color property of the SolidColorBrush as well.
Take your time to understand how the xaml is parsing and using these features and I believe it'll answer your question about how I'm referencing SubControlA and SubControlB from my MainControl at the beginning of this answer.
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 feel like I'm missing a fairly fundamental concept to WPF when it comes to databinding, but I can't seem to find the right combination of Google keywords to locate what I'm after, so maybe the SO Community can help. :)
I've got a WPF usercontrol that needs to databind to two separate objects in order to display properly. Both objects must be dynamically set from an outside source. Thus far I've simply been using the DataContext property of the form for dynamic object binding, but that only allows for one object to be referenced. I feel like this is a simple problem and that I must be missing something obvious.
My previous attempt looks something like this:
<UserControl.Resources>
<src:Person x:Key="personSource" />
<src:Job x:Key="jobSource" />
</UserControl.Resources>
<TextBox Text="{Binding Source={StaticResource personSource}, Path=Name" />
<TextBox Text="{Binding Source={StaticResource jobSource}, Path=Address" />
This will bind to any defaults I give the classes just fine, but If I try to dynamically set the objects in code (as I show below) I don't see any change.
Person personSource = FindResource("personSource") as Person;
personSource = externalPerson;
Job jobSource= FindResource("jobSource") as Job;
jobSource = externalJob;
What am I missing?
I would probably use a CustomControl with two DependencyProperties. Then the external site that uses your custom control could bind the data that they want to that control, also by using a custom control you can template the way the control looks in different situations.
Custom control code would look something like:
public class CustomControl : Control
{
public static readonly DependencyProperty PersonProperty =
DependencyProperty.Register("Person", typeof(Person), typeof(CustomControl), new UIPropertyMetadata(null));
public Person Person
{
get { return (Person) GetValue(PersonProperty); }
set { SetValue(PersonProperty, value); }
}
public static readonly DependencyProperty JobProperty =
DependencyProperty.Register("Job", typeof(Job), typeof(CustomControl), new UIPropertyMetadata(null));
public Job Job
{
get { return (Job) GetValue(JobProperty); }
set { SetValue(JobProperty, value); }
}
static CustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl)));
}
}
Generic.xaml is a file that should be created for you and could have a Style that looks something like this:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication3">
<Style TargetType="{x:Type local:CustomControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<TextBox Text="{TemplateBinding Person.Name}" />
<TextBox Text="{TemplateBinding Job.Address}" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Finally, when you go to use your control you would do something like this.
<src:CustomControl Person="{Binding Person}" Job="{Binding Job}" />
The reason your text boxes don't update is that you are binding them to a StaticResource. As the name implies these resources are static and don't post change notifications. And because Binding is a MarkupExtension and does not derive from DependencyObject you can't use a DynamicResource.
Try creating depedency properties on your control to reference the Person and Job objects.
Then set the DataContext of the UserControl to reference itself.
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Then you can use dot notation to reference the required properties.
<TextBox Text="{Binding Path=Person.Name" />
<TextBox Text="{Binding Path=Job.Address" />
Or use the source parameter
<TextBox Text="{Binding Source=Person, Path=Name" />
<TextBox Text="{Binding Source=Job, Path=Address" />