FlipView Datatemplate fails to bind on custom UserControl - c#

TL;DR
Using MahApps' FlipView with a custom DataTemplate, binding fails to update when using a custom UserControl.
Issue
Trying to use MahApps's FlipView to host a custom UserControl for each view. For testing purposes, my UserControl looks like this (testing this on MetroDemo sample project)
XAML
<UserControl x:Class="MetroDemo.ExampleViews.CustomImageControl"
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:MetroDemo.ExampleViews"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid x:Name="ControlContainer">
<Image Source="{Binding SourceImage}" Stretch="Fill"/>
</Grid>
</UserControl>
Code Behind
namespace MetroDemo.ExampleViews
{
/// <summary>
/// Interaction logic for CustomImageControl.xaml
/// </summary>
public partial class CustomImageControl : UserControl
{
public ImageSource SourceImage
{
get => (ImageSource)this.GetValue(SourceImageProperty);
set
{
this.SetValue(SourceImageProperty, value);
}
}
public static readonly DependencyProperty SourceImageProperty = DependencyProperty.Register(
nameof(SourceImage),
typeof(ImageSource),
typeof(CustomImageControl),
new PropertyMetadata(OnSourceImageChanged));
private static void OnSourceImageChanged(DependencyObject data, DependencyPropertyChangedEventArgs args)
{
var control = (CustomImageControl)data;
if (control is null || !(args.NewValue is ImageSource image))
return;
control.SourceImage = image;
}
public CustomImageControl()
{
InitializeComponent();
this.ControlContainer.DataContext = this;
}
}
}
Then, I'm using this custom user control within a DataTemplate that FlipView uses, replacing this bit here for:
<DataTemplate x:Key="ImageDataTemplate" x:Shared="False">
<local:CustomImageControl SourceImage="{Binding Mode=OneWay, FallbackValue={x:Static DependencyProperty.UnsetValue}, Converter={mah:NullToUnsetValueConverter}}" />
</DataTemplate>
When flipping to the next view, it animates but for some reason it keeps the same first bound image.
It seems that the binding is failing somewhere but I can't see where. Any help is highly appreciated!

if I comment out the following it works like expected:
private static void OnSourceImageChanged(DependencyObject data, DependencyPropertyChangedEventArgs args)
{
//var control = (CustomImageControl)data;
//if (control is null || !(args.NewValue is ImageSource image))
// return;
//control.SourceImage = image;
}
it basically gets a new value and sets it as a new ImageSource which will call the OnSourceImageChanged. In this void it sets a new ImageSource which will call this OnSourceImageChanged again.

Related

DependencyProperty for TextBox gives compile time error (UWP)

I have a textbox with a DependencyProperty, Code looks like this
<UserControl
x:Class="Projectname.Controls.Editors.EditTextControl"
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:ui="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
mc:Ignorable="d">
<Grid>
<TextBox PlaceholderText="I'am Active" HasError="{Binding IsInvalid, UpdateSourceTrigger=PropertyChanged}" Height="80" Width="300" x:Name="txtActive" Text="{Binding TextValue, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" ></TextBox>
</Grid>
public sealed partial class EditTextControl : UserControl
{
TestViewModel TV = new TestViewModel();
public EditTextControl()
{
this.InitializeComponent();
this.DataContext = TV;
}
public bool HasError
{
get { return (bool)GetValue(HasErrorProperty); }
set { SetValue(HasErrorProperty, value); }
}
/// <summary>
/// This is a dependency property that will indicate if there's an error.
/// This DP can be bound to a property of the VM.
/// </summary>
public static readonly DependencyProperty HasErrorProperty =
DependencyProperty.Register("HasError", typeof(bool), typeof(EditTextControl), new PropertyMetadata(false, HasErrorUpdated));
// This method will update the Validation visual state which will be defined later in the Style
private static void HasErrorUpdated(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
EditTextControl textBox = d as EditTextControl;
if (textBox != null)
{
if (textBox.HasError)
VisualStateManager.GoToState(textBox, "InvalidState", false);
else
VisualStateManager.GoToState(textBox, "ValidState", false);
}
}
}
All looks good to me, But on compile-time itself, it's giving these errors.
The property 'HasError' was not found in type 'TextBox'.
The member "HasError" is not recognized or is not accessible.
Can anyone please point out what I am doing wrong here?
HasError is a property on the EditTextControl user control, not on the TextBox.
If you want to add a custom property to the TextBox class, you use an Attached Property not a Dependency property.

Add a DependencyProperty to ThreadSeparatedImage class of Meta.Vlc library to follow MVVM model

I'm working on a Video Player application using C# and WPF.
I have to follow a MVVM model for this WPF project.
I want to use the Meta.Vlc library to display severals RTSP stream in a grid.
So, I add a "ThreadSeparatedImage" object in my VideoPlayControl XAML (view part of the model):
VideoPlayerControl.xaml:
<UserControl x:Class="TVSCS_View.VideoDisplay.VideoPlayerControl"
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:ctrl="clr-namespace:TVSCS_View.VideoDisplay"
xmlns:vlc="clr-namespace:Vlc.Wpf;assembly=Vlc.Wpf"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:helpers="clr-namespace:TVSCS_View.Helpers"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300"
x:Name="controlVideoDisplay"
DataContext="{Binding ElementName=controlVideoDisplay}">
<Border BorderBrush="Black"
BorderThickness="1">
<Grid x:Name="videoPlayerGrid"
Margin="5,5,5,5">
<TextBlock x:Name="videoNameText"
HorizontalAlignment="Left"
Margin="10,10,0,0"
Text="{Binding Path=VideoStreamName, Mode=OneWay}"
VerticalAlignment="Top" Width="100"/>
<vlc:ThreadSeparatedImage x:Name="videoSource"
ThreadImageSource={Binding Path=ImgSource}" />
</Grid>
</Border>
</UserControl>
Then, I have to implement a DependencyProperty to follow a MVVM model. So I modified Meta.Vlc "ThreadSeparatedImage.cs" class, adding the following code:
public static readonly DependencyProperty ThreadImageSourceProperty =
DependencyProperty.RegisterAttached("ThreadImageSource",
typeof(ImageSource),
typeof(ThreadSeparatedImage),
new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(ImageSourcePropertyChanged)));
private static void ImageSourcePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
ThreadSeparatedImage threadSeparatedImage = obj as ThreadSeparatedImage;
if (null != threadSeparatedImage)
{
threadSeparatedImage.Source = (ImageSource)args.NewValue;
}
}
public static void SetImageSource(UIElement element, ImageSource imageSource)
{
element.SetValue(ThreadImageSourceProperty, imageSource);
}
public static ImageSource GetImageSource(UIElement element)
{
return (ImageSource)element.GetValue(ThreadImageSourceProperty);
}
Finally, I have a view model associated to my "VideoPlayerControl" XAML, with the following properties and methods:
VideoPlayerViewModel.cs
private ImageSource _imageSource;
public ImageSource ImgSource
{
get { return _imageSource; }
set
{
if (true == SetProperty(ref _imageSource, value))
{
RaisePropertyChanged("ImgSource");
}
}
}
public void AddVLCPlayer(VlcPlayer mediaPlayer)
{
mediaPlayer.Stop();
mediaPlayer.LoadMedia(#"rtsp://10.2.92.110:554/profile5/media.smp");
mediaPlayer.Play();
ImgSource = mediaPlayer.VideoSource;
mediaPlayer.VideoSourceChanged += MediaPlayer_VideoSourceChanged;
}
private void MediaPlayer_VideoSourceChanged(object sender, VideoSourceChangedEventArgs e)
{
ImgSource = e.NewVideoSource;
}
"AddVLCPlayer" method could be called at initialization.
With this code, the video stream is neither displayed or updated.
Any idea? Thanks.
A Binding like
ThreadImageSource="{Binding Path=ImgSource}"
in the XAML of your UserControl requires that the DataContext of the control is an instance of your view model, i.e. class VideoPlayerViewModel.
This is usually achieved somewhere in your MainWindow code, where you might have something like this:
public MainWindow()
{
InitializeComponent();
DataContext = new VideoPlayerViewModel();
}
Then the DataContext is inherited by your UserControl somewhere in the MainWindow's XAML tree.
However, this will only work if the UserControl does not explicitly set its DataContext, as you do here:
<UserControl ... x:Name="controlVideoDisplay"
DataContext="{Binding ElementName=controlVideoDisplay}">
Remove that DataContext assignment. As a general rule, never set the DataContext of a UserControl explictly, regardless of what they tell you in online tutorials and blog posts.
Besides that, you should probably avoid modifying the code of the Vlc ThreadSeparatedImage class, and instead create a derived class with your dependency property:
public class MyThreadSeparatedImage : ThreadSeparatedImage
{
public static readonly DependencyProperty ThreadImageSourceProperty =
DependencyProperty.Register(
"ThreadImageSource",
typeof(ImageSource),
typeof(MyThreadSeparatedImage),
new FrameworkPropertyMetadata(null, ThreadImageSourcePropertyChanged));
private static void ThreadImageSourcePropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
((ThreadSeparatedImage)obj).Source = (ImageSource)args.NewValue;
}
public ImageSource ThreadImageSource
{
get { return (ImageSource)element.GetValue(ThreadImageSourceProperty); }
set { element.SetValue(ThreadImageSourceProperty, imageSource); }
}
}
I use a classic Image control too.
In XAML file (view part):
<Image Source="{Binding Path=VideoImageSource}" />
In my associated view model (VidePlayerViewModel.cs):
private ImageSource _videoImageSource;
public ImageSource VideoImageSource
{
get { return _videoImageSource; }
set
{
if (true == SetProperty(ref _videoImageSource, value))
{
RaisePropertyChanged("VideoImageSource");
}
}
}
public void AddVLCPlayer(string videoStreamPath)
{
//Create MediaPlayer:
VlcMediaPlayer = new VlcPlayer(false);
VlcMediaPlayer.Initialize(Globals.pathLibVlc);
//Start player
VlcMediaPlayer.Stop();
VlcMediaPlayer.LoadMedia(videoStreamPath);
VlcMediaPlayer.Play();
//Link VLC player to image source:
VideoImageSource = VlcMediaPlayer.VideoSource;
VlcMediaPlayer.VideoSourceChanged += MediaPlayer_VideoSourceChanged;
}

Button user control with Dependency property ShowImage display image path instead of Image

I'm trying to create a button user control which can display Image from xaml by adding property (ShowImage="ImagePath").
I've bound the user control Image source to the button's Content in the xaml file:
<UserControl x:Class="testUserControl.UserControls.TestDependencyShowImage"
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"
mc:Ignorable="d"
Height="auto" Width="auto">
<Grid>
<Button MinHeight="30" MinWidth="50">
<Button.Template>
<ControlTemplate TargetType="Button">
<Image Source="{TemplateBinding Content}"/>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</UserControl>
And I've created a Dependency property which create BitmapImage and set it to the content(in the meantime hard coded path just to see if it can be done).
cs:
namespace testUserControl.UserControls
{
/// <summary>
/// Interaction logic for TestDependencyShowImage.xaml
/// </summary>
public partial class TestDependencyShowImage : UserControl
{
private static BitmapImage s_oImage = null;
private static string s_strSourceImage = null;
public static readonly DependencyProperty ShowImageDP = DependencyProperty.Register("ShowImage", typeof(string), typeof(TestDependencyShowImage), new PropertyMetadata(null, new PropertyChangedCallback(SetImage)));
public string ShowImage
{
get
{
return (string)GetValue(ShowImageDP);
}
set
{
SetValue(ShowImageDP, value);
this.Content = s_oImage;
//OnTargetPowerChanged(this, new DependencyPropertyChangedEventArgs(TargetPowerProperty, value, value)); // Old value irrelevant.
}
}
private static void SetImage(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
TestDependencyShowImage muc = (TestDependencyShowImage)obj;
s_strSourceImage = (string)args.NewValue;
if (s_strSourceImage != null)
{
s_oImage = new BitmapImage(new Uri(#"C:\Users\AmitL\Desktop\james-brown-010.jpg", UriKind.Absolute));
//BitmapImage l_oImage = new BitmapImage(new Uri(value));
}
}
public TestDependencyShowImage()
{
InitializeComponent();
this.Content = s_oImage;
}
}
}
You really don't need any of that code. Just let the Framework convert the string file path into the image for you. Try this code inside your UserControl:
<Image Source="{Binding ShowImage, RelativeSource={RelativeSource
AncestorType={x:Type YourXamlNamespacePrefix:TestDependencyShowImage}}}" />

DependencyProperty Silverlight UserControl

I have read through a few articles on this and I can't see what im doing wrong here could anyone help :)
I have a UserControl called CreateRuleItemView I want to add a Dependency Property on here that I can bind my ViewModel too. So far I have.
public partial class CreateRuleItemView : UserControl
{
public CreateRuleItemView()
{
InitializeComponent();
}
public Boolean ShowEditTablePopup
{
get
{
return (Boolean)this.GetValue(ShowEditTablePopupProperty);
}
set
{
this.SetValue(ShowEditTablePopupProperty, value);
}
}
public static readonly DependencyProperty ShowEditTablePopupProperty = DependencyProperty.Register("ShowEditTablePopup", typeof(Boolean), typeof(CreateRuleItemView), new PropertyMetadata(null, OnShowEditTablePopupChanged));
private static void OnShowEditTablePopupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
}
}
If I then try to access the property in the User Control Xaml I get:
<UserControl x:Class="Views.Setup.CreateRuleItemView"
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"
d:DataContext="{d:DesignInstance Type=vm:CreateRuleItemViewModel, IsDesignTimeCreatable=False}"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" ShowEditTablePopup="{Binding DataContext.ShowEditTablePopup}" >
Error 1 The member "ShowEditTablePopup" is not recognized or is not accessible.
Error 3 The property 'ShowEditTablePopup' does not exist on the type 'UserControl'
Error 2 The property 'ShowEditTablePopup' was not found in type 'UserControl'.
Edit 1:
Ok Managed to get around this by adding the binding in the code behind on my Main window where i setup my view.
Setup.CreateRuleItemView v = new Setup.CreateRuleItemView();
BindingOperations.SetBinding(v, CreateRuleItemView.EditTablePopupProperty, new Binding("EditTablePopup"));
You won't be able to achieve this with a UserControl (I've just tried replacing the <UserControl... partial declaration in XAML with <local:CreateRuleItemView when recreating the code locally, but this results in a circular reference and thus won't compile/will potentially result in a XamlParseException). I'd write a control inheriting from ContentControl to which you can add the property and template it instead (I did this with WPF so the namespaces may differ, otherwise the code will work):
using System;
using System.Windows;
using System.Windows.Controls;
namespace DepPropTest
{
/// <summary>
/// Description of CreateRuleItemView.
/// </summary>
public class CreateRuleItemView : ContentControl
{
public CreateRuleItemView()
{
}
public static readonly DependencyProperty ShowEditTablePopupProperty =
DependencyProperty.Register("ShowEditTablePopup", typeof (bool), typeof (CreateRuleItemView), new PropertyMetadata());
public bool ShowEditTablePopup
{
get { return (bool) GetValue(ShowEditTablePopupProperty); }
set { SetValue(ShowEditTablePopupProperty, value); }
}
}
}
Then you can use it as follows (this example uses WPF by the way, hence Window being the parent control):
<Window x:Class="DepPropTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DepPropTest"
Title="DepPropTest" Height="300" Width="300">
<local:CreateRuleItemView Width="300"
Height="300"
ShowEditTablePopup="True">
<local:CreateRuleItemView.Template>
<ControlTemplate>
<!-- define your control's visual appearance... -->
</ControlTemplate>
</local:CreateRuleItemView.Template>
<TextBox Text="Some content for your view" />
</local:CreateRuleItemView>
</Window>

Updating UserControl children in DependencyProperty setter not working

I have a UserControl in my Silverlight application and for some reason, the DependencyProperty of a UserControl is not set if I bind it to a value in the view model. I spent several hours of debugging in a trial-and-error fashion now and I'm all out of ideas what to try next.
I can reproduce the issue in a new Silverlight project with
MainPage.xaml
<UserControl x:Class="SilverlightApplication1.MainPage"
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:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel>
<local:MyCtrl HeaderText="{Binding HeaderText}"/>
<TextBlock Text="{Binding HeaderText}"/>
</StackPanel>
</Grid>
</UserControl>
MainPage.xaml.cs
using System.Windows.Controls;
namespace SilverlightApplication1
{
public class Vm
{
public string HeaderText
{ get; set; }
}
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
DataContext = new Vm() { HeaderText = "My Header" };
}
}
}
MyCtrl.xaml (added as new "Silverlight User Control")
<UserControl x:Class="SilverlightApplication1.MyCtrl"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock x:Name="txtHeader" />
</Grid>
</UserControl>
MyCtrl.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace SilverlightApplication1
{
public partial class MyCtrl : UserControl
{
public MyCtrl()
{
InitializeComponent();
}
public static readonly DependencyProperty HeaderTextProperty = DependencyProperty.Register(
"HeaderText", typeof(string), typeof(MyCtrl), null);
public string HeaderText
{
get { return (string)GetValue(HeaderTextProperty); }
set
{
// NEVER CALLED
SetValue(HeaderTextProperty, value);
this.txtHeader.Text = value;
}
}
}
}
The rest of the project is used "as is", i.e. no compiler options were changed and the server part is also left "as is".
Observations:
I see that the getter of Vm.HeaderText is called during a binding
operation but the setter of the DependencyProperty MyCtrl.HeaderText is never called.
The TextBlock in MainPage below the custom control displays the bound value correctly.
There are no compiler warnings.
There are no exceptions thrown.
There are no debug outputs while the application runs.
This feels like something important is silently failing where it shouldn't.
Maybe I can shed some light on your observations and assumptions:
You can update child controls inside a DependencyProperty setter. Call your setter and see for yourself, the update will be performed. You falsely assume the binding engine is obliged to call your setter. Well, it just calls SetValue(HeaderTextProperty, newValue); on your control, no need to call the setter.
Your observation is specified behaviour.
As you figured out, the right way to do it is to use a propertyChanged callback.
The property getter on your viewmodel Vm.HeaderText is called by the binding engine, because your viewmodel is no DependencyObject and HeaderText is no DependencyProperty so there is no SetValue(...) and no GetValue(...)
You can't see any compiler warnings or exceptions, nor is anything silently failing, because there is nothing wrong with your scenario.
Well, seems like you cannot update the child controls inside a DependencyProperty setter (would be interesting to know why and if this is specified behaviour...)
Using this instead works:
public static readonly DependencyProperty HeaderTextProperty = DependencyProperty.Register(
"HeaderText", typeof(string), typeof(MyCtrl), new PropertyMetaData(OnHeaderTextChanged));
public string HeaderText
{
get { return (string)GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
private static void OnHeaderTextChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var ctrl = d as MyCtrl;
ctrl.txtHeader.Text = (string)e.NewValue;
}

Categories