WPF ItemsControl Binding to UserControl - wpf

I have a WPF application where I need to do something like that :
<ItemsControl x:Name="lstProducts">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProductName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I set the ItemsSource in code like this : lstProducts.ItemsSource = MyEFContext.Products;
Up to now everything is working fine. Now I want to use my own UserControl to display a product instead of the TextBlock like that.
<ItemsControl x:Name="lstProducts">
<ItemsControl.ItemTemplate>
<DataTemplate>
<my:ProductItemCtl ProductName="{Binding ProductName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In my userControl, I created a DependencyProperty like that (see below) where I set the ProductName in the OnProductNameChanged callback.
The TextBlock in my usercontrol is not updated when binding the ItemsControl, and the callback is not launched.
#region ProductName
/// <summary>
/// ProductName Dependency Property
/// </summary>
public static readonly DependencyProperty ProductNameProperty =
DependencyProperty.Register("ProductName", typeof(String), typeof(ProductItemCtl),
new FrameworkPropertyMetadata("",
new PropertyChangedCallback(OnProductNameChanged)));
/// <summary>
/// Gets or sets the ProductName property. This dependency property
/// indicates ....
/// </summary>
public String ProductName
{
get { return (String)GetValue(ProductNameProperty); }
set { SetValue(ProductNameProperty, value); }
}
/// <summary>
/// Handles changes to the ProductName property.
/// </summary>
private static void OnProductNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ProductItemCtl target = (ProductItemCtl)d;
String oldProductName = (String)e.OldValue;
String newProductName = target.ProductName;
target.OnProductNameChanged(oldProductName, newProductName);
}
/// <summary>
/// Provides derived classes an opportunity to handle changes to the ProductName property.
/// </summary>
protected virtual void OnProductNameChanged(String oldProductName, String newProductName)
{
// Set Product Name in the display Here!
this.txtProductName.Text = newProductName;
}
#endregion

Not an answer, but i can't write this as comment. What happens if you simplify your code like this:
public static readonly DependencyProperty ProductNameProperty = DependencyProperty.Register(
"ProductName", typeof(string), typeof(ProductItemCtl),
new FrameworkPropertyMetadata(string.Empty,
(o, e) => ((ProductItemCtl)o).txtProductName.Text = (string)e.NewValue));

Arrrghhh got it... I had this.ProductName = ""; in the constructor of my UserControl
I wasted so much time on this... And sorry for wasting all of yours.
Since I found out by trying your code Clemens I will mark your answer as "Answered".
thanks everyone.

Related

WPF MVVM - Collection shared between ViewModels for some reason

So I have a TabControl that has an instance of ViewModel1 as each tab. ViewModel1's View has a custom UserControl I built, that basically exposes a DependencyProperty "Images" that stores the list of images the control has. This property has been bound (OneWayToSource) to ViewModel1's property "Images".
The problem I'm having is that for some reason, all instances of ViewModel1 (all tabs) are sharing this property. So if Tab1 has 1 image in the control, and Tab2 has 3 images in the control, the "Images" property of each ViewModel1 instance has a collection of 4 images.
I don't know how anything like this could happen - anyone have any ideas?
Note that I'm using Caliburn.Micro as a MVVM framework.
EDIT: The property inside the control is defined like this:
public List<ImageData> Images
{
get { return (List<ImageData>)GetValue(ImagesProperty); }
set { return; }
}
public static readonly DependencyProperty ImagesProperty =
DependencyProperty.Register("Images", typeof(List<ImageData>), typeof(WebImageAlbum),
new UIPropertyMetadata(new List<ImageData>()));
New items are just added to that with Images.Add() and inside the View's XAML, this property is bound to the ViewModel's "Images" property with Mode=OneWayToSource.
EDIT: This is what the Tab view with the UserControl looks like:
<UserControl
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:NET_MD3.Views"
xmlns:cal="http://www.caliburnproject.org"
xmlns:CustomControls="clr-namespace:NET_MD3.CustomControls" x:Class="NET_MD3.Views.AlbumTabView"
mc:Ignorable="d"
d:DesignHeight="267.789" d:DesignWidth="473.684">
<Grid>
<CustomControls:WebImageAlbum x:Name="Album" Margin="10,10,10,50" Width="Auto" Height="Auto" ImageWidthHeight="95"
cal:Message.Attach="[Event ImageClicked] = [Action ImageClicked($eventArgs)]"
Images="{Binding Images, Mode=OneWayToSource}"/>
<Button x:Name="CloseTab" Content="Close tab and delete album" HorizontalAlignment="Left" Margin="10,0,0,10" VerticalAlignment="Bottom" Width="172" Height="30"/>
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="342,243,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>
</Grid>
This is what the ViewModel looks like:
public class AlbumTabViewModel : Screen, IAlbumTabItem
{
#region Constructor
public AlbumTabViewModel(int id)
{
this.TabID = id;
}
#endregion
#region Properties
/// <summary>
/// Get the Images loaded in this tab's Album (do not use the setter - it's for the Binding only)
/// </summary>
public List<ImageData> Images
{
get; set;
}
/// <summary>
/// The Display name of this Screen (Tab)
/// </summary>
public override string DisplayName
{
get
{
return $"Album #{this.TabID}";
}
set { base.DisplayName = value; }
}
/// <summary>
/// The ID of this tab
/// </summary>
public int TabID { get; private set; }
#endregion
#region Actions
/// <summary>
/// Delete this album tab
/// </summary>
public void CloseTab()
{
this.TryClose();
}
/// <summary>
/// An image has been clicked within the album
/// </summary>
/// <param name="e"></param>
public void ImageClicked(ImageData e)
{
//ttt
}
#endregion
}
Turns out the problem was in the declaration of the DependencyProperty. In the UIPropertyMetadata() that I passed to the static declaration of the property, I made it use a 'new List()' as the default value and turns out that you can't really do that, because it will create that List as a static object, which explains why every instance of the UserControl had the same list. This probably happens because the DependencyProperty itself is static and so when it tries to create that default value, it will be static as well.
I guess the lesson is don't put reference types as default values when declaring a new DependencyProperty, and if you must, set it elsewhere (within the CLR property that accesses the value, or something).

Altering border's corner radius on WPF Expander control?

Very high up the visual tree in WPF's Expander control is a border element (see screenshot). By default this has a CornerRadius of 3. Is it possible to modify this value?
I'll leave marking as answer for now but I managed to implement the solution as follows:
Using stylesnooper I obtained the style / control template used for the 'standard' Expander control.
Then after discovering it didn't quite behave as expected, figured out that the line <ToggleButton IsChecked="False" ... is wrong and should actually be <ToggleButton IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"...
Everything then worked as expected.
I made a behavior which modifies the first found border in the ControlTemplate. You can easily extend the behavior with new properties where u want to modify
/// <summary>
/// modifies the first found <see cref="Border"/> in the <see cref="ControlTemplate"/> of the attached <see cref="Control"/>
/// </summary>
public class ModifyBorderBehavior : Behavior<Control>
{
// ##############################################################################################################################
// Properties
// ##############################################################################################################################
#region Properties
/// <summary>
/// The new corner radius
/// </summary>
public CornerRadius CornerRadius
{
get => (CornerRadius)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
/// <summary>
/// The <see cref="CornerRadius"/> DependencyProperty.
/// </summary>
public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(ModifyBorderBehavior));
#endregion
// ##############################################################################################################################
// Constructor
// ##############################################################################################################################
#region Constructor
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += _OnLoaded;
}
private void _OnLoaded(object sender, RoutedEventArgs e)
{
//var children = VisualTree.GetVisualChildCollection<Border>(sender);
if (sender is Control control)
{
Border border = VisualTree.GetVisualChild<Border>(control);
if(ReadLocalValue(CornerRadiusProperty) != DependencyProperty.UnsetValue)
border.CornerRadius = CornerRadius;
}
}
#endregion
}
<Expander>
<i:Interaction.Behaviors>
<zls:ModifyBorderBehavior CornerRadius="0"/>
</i:Interaction.Behaviors>
</Expander>

What is the best way to diagnose WPF UserControl DependencyProperty issues?

Here is my issue, I created a UserControl as follows:
XAML:
<UserControl x:Class="ProcessVisualizationBar.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"
mc:Ignorable="d" xmlns:lb="clr-namespace:ProcessVisualizationBar"
Name="ProcessVisualizationBar">
<Border BorderBrush="Silver" BorderThickness="1,1,1,1" Margin="0,5,5,5" CornerRadius="5" Padding="2">
<ListBox Name="ProcessVisualizationRibbon" Grid.Column="1" Height="40" ItemsSource="{Binding ElementName=ProcessVisualizationBar, Path=ItemsSource}"/>
</Border>
</UserControl>
Code Behind(C#):
using System.Windows;
using System.Windows.Controls;
namespace ProcessVisualizationBar
{
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(System.Collections.IEnumerable), typeof(UserControl));
public System.Collections.IEnumerable ItemsSource
{
get { return ProcessVisualizationRibbon.ItemsSource; }
set { ProcessVisualizationRibbon.ItemsSource = value; }
}
public UserControl1()
{
InitializeComponent();
}
}
}
I build my Usercontrol and add the .dll to the reference of another project. I add the reference at the top of my XAML as such:
xmlns:uc="clr-namespace:ProcessVisualizationBar;assembly=ProcessVisualizationBar"
Then I go to use the control.
<uc:UserControl1 Grid.Row="2" x:Name="ProcessVisualizationContent" />
It finds the control okay, but when I try and find the ItemsSource Property I added to it, I'm not finding it. I'm not sure what I missed, and I'm not sure what debug tools are really available to figure this out.
Anyone have some experience with this that can share their wisdom?
What is the actual data being passed? That is what you should be creating and not a pass through situation which you are attempting.
Create a dependency property targetting the actual data to be passed with a property changed handler. On the change event, then call internal code to bind it to the ProcessVisualazation ItemsSource. That way you can debug when the data comes through by placing a breakpoint in the event.
Here is an example where the consumer will see StringData in the Xaml and needs to pas a list of strings into the custom control:
#region public List<string> StringData
/// <summary>
/// This data is to be bound to the ribbon control
/// </summary>
public List<string> StringData
{
get { return GetValue( StringDataProperty ) as List<string>; }
set { SetValue( StringDataProperty, value ); }
}
/// <summary>
/// Identifies the StringData dependency property.
/// </summary>
public static readonly System.Windows.DependencyProperty StringDataProperty =
System.Windows.DependencyProperty.Register(
"StringData",
typeof( List<string> ),
typeof( UserControl ),
new System.Windows.PropertyMetadata( null, OnStringDataPropertyChanged ) );
/// <summary>
/// StringDataProperty property changed handler.
/// </summary>
/// <param name="d">DASTreeBinder that changed its StringData.</param>
/// <param name="e">Event arguments.</param>
private static void OnStringDataPropertyChanged( System.Windows.DependencyObject d, System.Windows.DependencyPropertyChangedEventArgs e )
{
UserControl source = d as UserControl;
List<string> value = e.NewValue as List<string>;
BindDataToRibbon( value );
}
#endregion public List<string> StringData
Now just create a BindDataToRibbon method which will do the dirty work. Note that I use Jeff Wilcox's Silverlight dependency snippets in Visual Studio to generate the above dependency. I have used it for WPF and Silverlight projects.

How to Bind ListView ItemsSource using XAML not in Code behind.?

I am following MVVM Pattern and I want to bind ListView ItemsSource using XAML, not in even
this.Datacontext = ObservableCollection property.
My code is like this:
<ListView x:Name="MenuBarList"
Grid.Row="2"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Path=Menu.Option}"
Width="{Binding MainMenuWidth}"
SelectedItem="{Binding Path=SelectedMainMenuOption, Mode=TwoWay}" >
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" IsHitTestVisible="False" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
and the Menu is the property and it will seat on the ViewModel. Option is the Class of the Property, so I am using Menu.Option
My Menu is the property of ContentMenuModel type and ContentMenuModel is the class which contain the property of Option and Title and Image.
See the property of the Menu which is inside of the ViewModel
public const string MenuPropertyName = "Menu";
private ContentMenuModel _Menu = null;
/// <summary>
/// Gets the Menu collection.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public ContentMenuModel Menu
{
get
{
return _Menu;
}
set
{
if (_Menu == value)
return;
_Menu = value;
// Update bindings, no broadcast
RaisePropertyChanged(MenuPropertyName);
}
}
And the ContentMenuModel class looks like this:
public class ContentMenuModel
{
#region Title
/// <summary>
/// The <see cref="Title" /> property's name.
/// </summary>
public const string TitlePropertyName = "Title";
private string _Title = String.Empty;
/// <summary>
/// Gets the Title property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
[Required]
[StringLength(128, ErrorMessage = "The Title value cannot exceed 128 characters. ")]
public string Title
{
get
{
return _Title;
}
set
{
if (_Title == value)
{
return;
}
var oldValue = _Title;
_Title = value;
// Update bindings, no broadcast
RaisePropertyChanged(TitlePropertyName);
}
}
#endregion
#region Options
/// <summary>
/// The <see cref="Options" /> property's name.
/// </summary>
public const string OptionsPropertyName = "Options";
private ObservableCollection<ContentMenuOptionModel> _Options = null;
/// <summary>
/// Gets the Options property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public ObservableCollection<ContentMenuOptionModel> Options
{
get
{
return _Options;
}
set
{
if (_Options == value)
{
return;
}
var oldValue = _Options;
_Options = value;
RaisePropertyChanged(OptionsPropertyName);
}
}
#endregion
#region ContextText
/// <summary>
/// The <see cref="Options" /> property's name.
/// </summary>
public const string ContextTextPropertyName = "ContextText";
private ContentPageItem _ContextText = null;
/// <summary>
/// Gets the ContextText property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public ContentPageItem ContextText
{
get
{
return _ContextText;
}
set
{
if (_ContextText == value)
{
return;
}
_ContextText = value;
RaisePropertyChanged(OptionsPropertyName);
}
}
#endregion
}
I had binded the ViewModelLocator to my mainwindow's DataContext and Path=ViewModel's MainMenu, so the MainMain is the object of the ViewModel where I can bind this Property to the ListView's ItemsSource, but it is not working.
Please correct me where I am wrong.
In order to get full example quickly, you can install NUGet for Visual Studio and install MVVMLight package through it on a clean WPF Application project. Then it will set everything up, and you will see how it works.
Though I'll describe the basics here.
MVVMLight supports this out of the box. Standard template for MVVMLigth includes ViewModelLocator and a MainViewModel. ViewModelLocator - is the class that holds all other view models. From the start it has only one property public MainViewModel Main {get;}. ViewModelLocator is registered as a resource in App.xaml
<Application>
<Application.Resources>
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
</Application.Resources>
</Application>
Then, on any page, if you want to get to the view model, you should simply reference Locator resource and get its property that is appropriate for your page. Here's example for MainWindow and its MainViewModel:
<Window DataContext="{Binding Source={StaticResource Locator}, Path=Main}">
<Grid>
<TextBlock Text="{Binding Text}"/>
</Grid>
</Window>
In the example above, I've added public string Text {get;} property to MainViewModel and referenced. In example there is no need for any code-behind, everyting is setup declaratively via xaml.
Simply you add the Grid of you ListView and assing the DataContext to the source and it will be working.

How to detect a change in the Text property of a TextBlock?

Is there any way to detect a change in the Text property of a TextBlock element using events?
(I'm trying to provide an animation for highlighting the TextBlocks whose Text property change within a DataGrid)
It's easier than that! Late answer, but much simpler.
// assume textBlock is your TextBlock
var dp = DependencyPropertyDescriptor.FromProperty(
TextBlock.TextProperty,
typeof(TextBlock));
dp.AddValueChanged(textBlock, (sender, args) =>
{
MessageBox.Show("text changed");
});
This is like the code from the link in bioskope's answer, but simplified. You need the TargetUpdated event and add NotifyOnTargetUpdated=True to the binding.
<TextBlock Text="{Binding YourTextProperty, NotifyOnTargetUpdated=True}"
TargetUpdated="YourTextEventHandler"/>
As far as I can understand there isn't any textchanged event in TextBlock. Looking at your requirement, I feel that re-templating a textbox will also not be a viable solution. From my preliminary searching around, this seems to be a possible solution.
<TextBlock x:Name="tbMessage" Text="{Binding Path=StatusBarText, NotifyOnTargetUpdated=True}">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="0:0:0″
To="1.0″ />
<DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="0:0:2″
From="1.0″ To="0.0″ BeginTime="0:0:5″ />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
Bind the Text property to a DependencyProperty, which has an event trigger:
public static string GetTextBoxText(DependencyObject obj)
{
return (string)obj.GetValue(TextBoxTextProperty);
}
public static void SetTextBoxText(DependencyObject obj, string value)
{
obj.SetValue(TextBoxTextProperty, value);
}
public static readonly DependencyProperty TextBoxTextProperty =
DependencyProperty.RegisterAttached(
"TextBoxText",
typeof(string),
typeof(TextBlockToolTipBehavior),
new FrameworkPropertyMetadata(string.Empty, TextBoxTextChangedCallback)
);
private static void TextBoxTextChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock textBlock = d as TextBlock;
HandleTextChange(textBlock);
}
In the XAML Bind to the TextBlock text Property:
<TextBlock
Text="{Binding SomeProperty, UpdateSourceTrigger=PropertyChanged}"
th:TextBlockBehavior.TextBoxText="{Binding Text,
RelativeSource={RelativeSource Self}}" />
Here is a similar example on MSDN using code-behind:
http://msdn.microsoft.com/en-us/library/system.windows.data.binding.targetupdated.aspx
Here's something you can use I picked up from Jerry Nixon and Daren May at the Microsoft Virtual Academy "Developing Universal Windows Apps with C# and XAML" and the code that contains the DependencyObject logic is here "(W8.1-WP8.1) UNIVERSAL APP FOR MVA".
namespace App1.Behaviors
{
// <summary>
/// Helper class that allows you to monitor a property corresponding to a dependency property
/// on some object for changes and have an event raised from
/// the instance of this helper that you can handle.
/// Usage: Construct an instance, passing in the object and the name of the normal .NET property that
/// wraps a DependencyProperty, then subscribe to the PropertyChanged event on this helper instance.
/// Your subscriber will be called whenever the source DependencyProperty changes.
/// </summary>
public class DependencyPropertyChangedHelper : DependencyObject
{
/// <summary>
/// Constructor for the helper.
/// </summary>
/// <param name="source">Source object that exposes the DependencyProperty you wish to monitor.</param>
/// <param name="propertyPath">The name of the property on that object that you want to monitor.</param>
public DependencyPropertyChangedHelper(DependencyObject source, string propertyPath)
{
// Set up a binding that flows changes from the source DependencyProperty through to a DP contained by this helper
Binding binding = new Binding
{
Source = source,
Path = new PropertyPath(propertyPath)
};
BindingOperations.SetBinding(this, HelperProperty, binding);
}
/// <summary>
/// Dependency property that is used to hook property change events when an internal binding causes its value to change.
/// This is only public because the DependencyProperty syntax requires it to be, do not use this property directly in your code.
/// </summary>
public static DependencyProperty HelperProperty =
DependencyProperty.Register("Helper", typeof(object), typeof(DependencyPropertyChangedHelper), new PropertyMetadata(null, OnPropertyChanged));
/// <summary>
/// Wrapper property for a helper DependencyProperty used by this class. Only public because the DependencyProperty syntax requires it.
/// DO NOT use this property directly.
/// </summary>
public object Helper
{
get { return (object)GetValue(HelperProperty); }
set { SetValue(HelperProperty, value); }
}
// When our dependency property gets set by the binding, trigger the property changed event that the user of this helper can subscribe to
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var helper = (DependencyPropertyChangedHelper)d;
helper.PropertyChanged(d, e);
}
/// <summary>
/// This event will be raised whenever the source object property changes, and carries along the before and after values
/// </summary>
public event EventHandler<DependencyPropertyChangedEventArgs> PropertyChanged = delegate { };
}
}
Usage XAML:
<TextBlock Grid.Row="0"
x:Name="WritingMenuTitle"
HorizontalAlignment="Left"
FontSize="32"
FontWeight="SemiBold"
Text="{Binding WritingMenu.Title}"
TextAlignment="Left"
TextWrapping="Wrap"/>
Usage xaml.cs:
Behaviors.DependencyPropertyChangedHelper helper = new Behaviors.DependencyPropertyChangedHelper(this.WritingMenuTitle, Models.CommonNames.TextBlockText);
helper.PropertyChanged += viewModel.OnSenarioTextBlockTextChangedEvent;
Usage viewmodel.cs:
public async void OnSenarioTextBlockTextChangedEvent(object sender, DependencyPropertyChangedEventArgs args)
{
StringBuilder sMsg = new StringBuilder();
try
{
Debug.WriteLine(String.Format(".....WritingMenuTitle : New ({0}), Old ({1})", args.NewValue, args.OldValue));
}
catch (Exception msg)
{
#region Exception
.....
#endregion
}
}

Resources