Set Property in Viewmodel from Code Behind - c#

I think I am overthinking this at this point, but I have a unique situation.
Using a treeviewI am displaying a table and the fields in that table:
XAML:
<TreeView x:Name="myTreeView" PreviewMouseDoubleClick="myTreeView_MouseLeftButtonDown" SelectedValuePath="Name" ItemsSource="{Binding Fields}" ItemContainerStyle="{StaticResource TreeViewItemExpandedStyle}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Fields}">
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="Black" Text="{Binding Table}" />
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button BorderBrush="Transparent"
Background="White"
Command="">
<StackPanel>
<Path Margin="5"
Data="M0,5 H10 M5,5 V10Z"
Stroke="#2283B4"
StrokeThickness="1"
Height="10"
Width="10" />
</StackPanel>
</Button>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Height="0" Width="0" Visibility="Collapsed" Text="{Binding Table}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<!--<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding IsExpanded}" Value="True">
<Setter TargetName="treeIcon"
Property="Data"
Value="M0,5 H10"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>-->
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Example:
These treeviews are also their own user controls. The issue I am having is I need to be able to use the selected treeview item in another viewmodel. I also have found no other way to grab the selected treeview item and parent except through the code behind. Below is my entire code behind section for this control.
SourceRowUserControl.xaml.cs:
using Alliance.FromAnywhereControl.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Alliance.FromAnywhereControl.ViewModels;
using System.Reflection;
namespace Alliance.FromAnywhereControl
{
/// <summary>
/// Interaction logic for SourceRowUserControl.xaml
/// </summary>
public partial class SourceRowUserControl : UserControl
{
public SourceRowUserControl()
{
InitializeComponent();
}
private ObservableCollection<ConversionRowUserControl> ConversionTypes = new ObservableCollection<ConversionRowUserControl>();
public ObservableCollection<TableInformation> Fields
{
get { return (ObservableCollection<TableInformation>)GetValue(FieldsProperty); }
set { SetValue(FieldsProperty, value); }
}
// Using a DependencyProperty as the backing store for Fields. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FieldsProperty =
DependencyProperty.Register("Fields", typeof(ObservableCollection<TableInformation>), typeof(SourceRowUserControl), new PropertyMetadata(TableInformation.GetAll(null, null, null, null)));
public String FileTypeImage
{
get { return (String)GetValue(FileTypeImageProperty); }
set { SetValue(FileTypeImageProperty, value); }
}
// Using a DependencyProperty as the backing store for FieldTypeImage. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FileTypeImageProperty =
DependencyProperty.Register("FileTypeImage", typeof(String), typeof(SourceRowUserControl), new PropertyMetadata("File Type Image"));
public SolidColorBrush SpacerColor
{
get { return (SolidColorBrush)GetValue(SpacerColorProperty); }
set { SetValue(SpacerColorProperty, value); }
}
// Using a DependencyProperty as the backing store for SpacerColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SpacerColorProperty =
DependencyProperty.Register("SpacerColor", typeof(SolidColorBrush), typeof(SourceRowUserControl), new PropertyMetadata(new SolidColorBrush(Colors.Red)));
public String MiddleLabel
{
get { return (String)GetValue(MiddleLabelProperty); }
set { SetValue(MiddleLabelProperty, value); }
}
// Using a DependencyProperty as the backing store for MiddleLabel. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MiddleLabelProperty =
DependencyProperty.Register("MiddleLabel", typeof(String), typeof(SourceRowUserControl), new PropertyMetadata("Middle Label"));
private void myTreeView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
try
{
Field selectedField = new Field();
selectedField = (Field)myTreeView.SelectedItem;
ConversionViewModel cvm = new ConversionViewModel();
cvm.AddConversionType(selectedField.Table, selectedField.Name);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
}
The myTreeViewLeftButtonDown event is where is I am grabbing the selected field and table. As you see, I am then calling a method in another viewmodel, but this will not work how I want it to since it creates a new instance. I just put that there to basically show what I was wanting to do. Overview, I need to be able to use a field in my code behind in a viewmodel that is not connected to the view.
Please let me know if you need anymore information or would like to see more code. Thanks in advance!

Looking at your code, instead of defining new ConversionViewModel(), grab the instance of the ConversionViewModel and then set property on it.
public class ConversionViewModel()
{
private static ConversionViewModel _this;
public ConversionViewModel()
{
InitializeComponent();
_this = this;
}
public static ConversionViewModel GetInstance()
{
return _this;
}
//Other prop and methods
}
private void myTreeView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
try
{
Field selectedField = new Field();
selectedField = (Field)myTreeView.SelectedItem;
ConversionViewModel.GetInstance().AddConversionType(selectedField.Table, selectedField.Name);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

You can give your ItemTemplate an InputBinding to the Left Button Click action.
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{Binding DataContext.SomeViewModelCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TreeView}}"
CommandParameter="{Binding}"/>
</StackPanel.InputBindings>
...
</StackPanel>
</DataTemplate>
To help explain this, the MouseBinding specifies what command to execute when the LeftClick occurs on the StackPanel.
The Command is bound to the DataContext.SomeViewModelCommand from your TreeView.
The CommandParameter is the Field bound to the DataTemplate.
Now, all you need to do is declare a Command in your View Model (SomeViewModelCommand) where the Command Parameter will be your Field.

Related

WPF databinding of ICommand "freezes" after multiple Button clicks

This is my first time trying to databind an ICommand. I have a digital LED control that I would like to act like a Button, so I changed the DataTemplate for a Button control to look like an LED:
LED.xaml
<UserControl x:Class="LedControlDatabindingTest.LED"
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="root"
mc:Ignorable="d"
Height="Auto" Width="Auto">
<Grid DataContext="{Binding ElementName=root}">
<StackPanel Orientation="{Binding LEDOrientation, FallbackValue=Vertical}">
<!-- LED portion -->
<Button BorderBrush="Transparent" Background="Transparent" Click="Button_Click">
<Button.ContentTemplate>
<DataTemplate>
<Grid>
<Ellipse Grid.Column="0" Margin="3" Height="{Binding ElementName=root, Path=LEDSize, FallbackValue=16}"
Width="{Binding ElementName=root, Path=LEDSize, FallbackValue=16}"
Fill="{Binding ElementName=root, Path=LEDColor, FallbackValue=Green}"
StrokeThickness="2" Stroke="DarkGray" HorizontalAlignment="Center" />
<Ellipse Grid.Column="0" Margin="3" Height="{Binding ElementName=root, Path=LEDSize, FallbackValue=16}"
Width="{Binding ElementName=root, Path=LEDSize, FallbackValue=16}" HorizontalAlignment="Center">
<Ellipse.Fill>
<RadialGradientBrush GradientOrigin="0.5,1.0">
<RadialGradientBrush.RelativeTransform>
<TransformGroup>
<ScaleTransform CenterX="0.5" CenterY="0.5" ScaleX="1.5" ScaleY="1.5"/>
<TranslateTransform X="0.02" Y="0.3"/>
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
<GradientStop Offset="1" Color="#00000000"/>
<GradientStop Offset="0.4" Color="#FFFFFFFF"/>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Grid>
</DataTemplate>
</Button.ContentTemplate>
</Button>
<!-- label -->
<TextBlock Grid.Column="1" Margin="3" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding LEDLabel, FallbackValue=0}" />
</StackPanel>
</Grid>
</UserControl>
I want the host application to be able to databind to properties like size, color, and label of the LED. In addition, I want to be able to bind to a command handler.
LED.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace LedControlDatabindingTest {
/// <summary>
/// Interaction logic for LED.xaml
/// </summary>
public partial class LED : UserControl
{
public static DependencyProperty LEDColorProperty = DependencyProperty.Register( "LEDColor", typeof(Brush), typeof(LED));
public Brush LEDColor
{
get { return this.GetValue(LEDColorProperty) as Brush; }
set {
this.SetValue( LEDColorProperty, value);
}
}
public static DependencyProperty LEDSizeProperty = DependencyProperty.Register( "LEDSize", typeof(int), typeof(LED));
public int LEDSize
{
get { return (int)GetValue(LEDSizeProperty); }
set {
SetValue( LEDSizeProperty, value);
}
}
public static DependencyProperty LEDLabelProperty = DependencyProperty.Register( "LEDLabel", typeof(string), typeof(LED));
public string LEDLabel
{
get { return (string)GetValue(LEDLabelProperty); }
set {
SetValue( LEDLabelProperty, value);
}
}
public static DependencyProperty LEDOrientationProperty = DependencyProperty.Register( "LEDOrientation", typeof(Orientation), typeof(LED));
public Orientation LEDOrientation
{
get { return (Orientation)GetValue(LEDOrientationProperty); }
set {
SetValue( LEDOrientationProperty, value);
}
}
public static readonly DependencyProperty LEDClickedProperty = DependencyProperty.Register("LEDClicked", typeof(ICommand), typeof(LED), new PropertyMetadata(null));
public ICommand LEDClicked
{
get { return (ICommand)GetValue(LEDClickedProperty); }
set { SetValue( LEDClickedProperty, value); }
}
public LED()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
LEDClicked.Execute( null);
}
}
}
My test application is simple.
MainWindow.xaml:
<Window x:Class="LedControlDatabindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LedControlDatabindingTest"
Title="MainWindow" Height="70" Width="250">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Digital Inputs:" />
<ListBox ItemsSource="{Binding AvailableDigitalInputs}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<local:LED LEDLabel="{Binding Index}" LEDColor="{Binding Color}" LEDSize="12" LEDClicked="{Binding Clicked}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</StackPanel>
</Window>
There are so no-nos in my code-behind, like the DataContext for my application, but I think for the purposes of this demo it's okay for now.
MainWindow.xaml.cs:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace LedControlDatabindingTest {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private class DigitalInputData : ViewModelBase
{
private Brush _color;
public Brush Color
{
get { return _color; }
set {
_color = value;
RaisePropertyChanged();
}
}
public int Index { get; set; }
public ICommand Clicked { get; set; }
private bool _state;
public DigitalInputData( int index, Brush on_color)
{
Index = index;
Color = Brushes.LightGray;
Clicked = new RelayCommand( () => {
// get current state of this digital input and then toggle it
_state = !_state;
// read back and update here until I get threaded updates implemented
Color = _state ? on_color : Brushes.LightGray;
});
}
}
private List<DigitalInputData> _inputs = new List<DigitalInputData>();
public ICollectionView AvailableDigitalInputs { get; set; }
public MainWindow()
{
InitializeComponent();
// For this example only, set DataContext in this way
DataContext = this;
for( int i=0; i<4; i++) {
_inputs.Add( new DigitalInputData( i, Brushes.Green));
}
AvailableDigitalInputs = CollectionViewSource.GetDefaultView( _inputs);
}
}
}
When I run this application, everything renders properly and according to my databound properties. The click handler works as well, and toggles the state of the LED.
But when I click the LED button numerous times, at some point (maybe after 20 clicks or so), it stops calling my databound ICommand. Why?
I got lucky and figured out a solution to the "freezing" problem, although I do not understand the technical reasoning.
In my DigitalInputData constructor, I created the handler for the RelayCommand using a lambda function instead of passing a reference to a handler. Once I switched over to passing the handler to the RelayCommand constructor, it worked great.

Creating generalized user controls with MVVM Light

How to create a general user control using MVVM Light?
All the main views in the application seem to work fine. However, general controls doesn't seem to accept bindings. This is my FileDiplay control. An icon and a TextBlock displaying a filename next to it.
Utilization
In one of the main views, I try to bind a FileName inside an ItemsTemplate of an ItemsControl. Specifying a literal, like FileName="xxx" works fine, but binding doesn't.
<local:FileLink FileName="{Binding FileName}" />
I've been playing around with DependencyProperty and INotifyPropertyChanged a lot. And seemingly there's no way around a DependencyProperty, since it can't be bound otherwise. When using a simple TextBlock instead of this user control, binding is accepted.
I didn't include the locator or the utilizing control in order to avoid too much code. In fact, I think this is a very simple problem that I haven't found the solution for, yet. I do think that having the DataContext set to the ViewModel is correct, since no list binding or real UserControl separation is possible. I've also debugged into the setters and tried the different approaches.
FileLink.xaml
<local:UserControlBase
x:Class="....FileLink"
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:..."
mc:Ignorable="d" DataContext="{Binding FileLink, Source={StaticResource Locator}}">
<Grid>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Icon}" Margin="0,0,5,0" />
<TextBlock Text="{Binding FileName}" />
</StackPanel>
</Grid>
</local:UserControlBase>
FileLink.xaml.cs
using System.Windows;
using System.Windows.Media;
namespace ...
{
public partial class FileLink : UserControlBase
{
private FileLinkViewModel ViewModel => DataContext as FileLinkViewModel;
public static DependencyProperty FileNameProperty = DependencyProperty.Register(nameof(FileName), typeof(string), typeof(FileLink));
public ImageSource Icon
{
get
{
return App.GetResource("IconFileTypeCsv.png"); // TODO:...
}
}
public string FileName
{
get
{
return ViewModel.FileName;
}
set
{
ViewModel.FileName = value;
}
}
public FileLink()
{
InitializeComponent();
}
}
}
FileLinkViewModel.cs
using GalaSoft.MvvmLight;
namespace ...
{
public class FileLinkViewModel : ViewModelBase
{
private string _FileName;
public string FileName
{
get
{
return _FileName;
}
set
{
Set(() => FileName, ref _FileName, value);
}
}
}
}
Do not explicitly set the DataContext of your UserControl, because it effectively prevents that the control inherits the DataContext from its parent control, which is what you expect in a Binding like
<local:FileLink FileName="{Binding FileName}" />
Also, do not wrap the view model properties like you did with the FileName property. If the view model has a FileName property, the above binding works out of the box, without any wrapping of the view model.
If you really need a FileName property in the UserControl, it should be a regular dependency property
public partial class FileLink : UserControlBase
{
public FileLink()
{
InitializeComponent();
}
public static readonly DependencyProperty FileNameProperty =
DependencyProperty.Register(nameof(FileName), typeof(string), typeof(FileLink));
public string FileName
{
get { return (string)GetValue(FileNameProperty); }
set { SetValue(FileNameProperty, value); }
}
}
and you should bind to it by specifying the UserControl as RelativeSource:
<local:UserControlBase ...> <!-- no DataContext assignment -->
<StackPanel Orientation="Horizontal">
<Image Source="IconFileTypeCsv.png" Margin="0,0,5,0" />
<TextBlock Text="{Binding FileName,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
</StackPanel>
</local:UserControlBase>

How to expose DependencyProperties within 2 ItemsSource lists (doesn't propagate)

Got a StackPanel/ItemsSource within another StackPanel/ItemsSource. The inner one has a Value that updates. Cannot get it to propagate an update to the UI.
Note: As a Registered DependencyProperty - it never updates. As a simple Property {get;set;} it updates ONCE then never again. What should it or they be in order to propagate?
Already checked numerous websites, books, etc - their examples don't demonstrate this use case or work.
What is missing here (just in the propagation)?
NOTE! (if it's not clear) - This is a paired-down fully-running sample to illustrate the problem. Should be clear this isn't even close to prod, let alone int or dev.
(UPDATE: Appears to be a Windows Store app specific issue as it works in std WPF/etc)
SAMPLE DependencyProperty code (.cs):
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace TestDependency
{
public sealed partial class MainPage : Page
{
private TopData topData;
public MainPage()
{
this.InitializeComponent();
this.Do();
}
public async void Do() {
topData = new TopData();
this.TopItemsControl.ItemsSource = topData;
var dispatcher = Dispatcher;
var action = new Action(async () =>
{
while (true)
{
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
topData[0][1].Value = topData[0][1].Value + 1;
});
await Task.Delay(1000);
}
});
await Task.Run(action);
}
}
public class TopData : ObservableCollection<MiddleData>
{
public TopData()
{
this.Add(new MiddleData("ABC", new[] {"a1", "a2", "a3"}));
this.Add(new MiddleData("DEF", new[] {"d1", "d2", "d3"}));
this.Add(new MiddleData("GHI", new[] {"g1", "g2", "g3"}));
}
}
public class MiddleData : ObservableCollection<BottomData>
{
public string Name { get; set; }
public MiddleData(string name, string[] list)
{
this.Name = name;
foreach (var item in list)
{
this.Add(new BottomData(item, 0));
}
}
}
public class BottomData : DependencyObject
{
public string Name { get; set; }
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(BottomData), new PropertyMetadata(0d));
public double Value
{
get { return (double)this.GetValue(ValueProperty); }
set { base.SetValue(ValueProperty, value); }
}
public BottomData(string name, double value)
{
this.Name = name;
this.Value = value;
}
}
}
and the SAMPLE xaml code to match:
<Page
x:Class="TestDependency.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TestDependency"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid x:Name="TopGrid">
<Grid.RowDefinitions>
<RowDefinition Height="5*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid.Resources>
<DataTemplate x:Key="StackItemTemplate">
<StackPanel x:Name="BottomStackPanel" Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Path=Value}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="SensorDataTemplate">
<StackPanel x:Name="TopStackPanel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20*"/>
<ColumnDefinition Width="10*"/>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding Path=Name}" Grid.Column="0"/>
<StackPanel x:Name="MiddleStackPanel" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center">
<ItemsControl x:Name="BottomItemsControl" ItemsSource="{Binding}" ItemTemplate="{StaticResource StackItemTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
</Grid>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ItemsControl x:Name="TopItemsControl" ItemTemplate="{StaticResource SensorDataTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</Grid>
</Page>
A couple of highlights:
First, I'm not quite familiar with Windows Phone development: I imported your code into a WPF application. The declaration of xmlns:local as "using:TestDependency" gave me the first punch in the face; I had to replace it with "clr-namespace:TestDependency".
Second, where you had:
<ItemsControl x:Name="BottomItemsControl" ItemsSource="{Binding Path=This}" ...
I changed it into:
<ItemsControl x:Name="BottomItemsControl" ItemsSource="{Binding}"...
Note the removal of the Path=This bit from the binding declaration. This way, everything is working just fine for me: the view is updating sequentially with the incremental values coming from the while loop in the async task in your Do() method.
Give it a try, please.
BottomData needs to implement INotifyPropertyChanged and fire PropertyChanged for Value. This is a difference between WPF (which hooks up DPs more deeply) and other Xaml frameworks such as Windows.UI.Xaml and (I believe) Silverlight.
See Property functionality provided by a dependency property in the Dependency properties overview on MSDN's Windows Dev Center:
Wiring the binding is not the only thing that's needed for most data
binding scenarios. For a one-way or two-way binding to be effective,
the source property must support change notifications that propagate
to the binding system and thus the target. For custom binding sources,
this means that the property must support INotifyPropertyChanged.
This can be done fairly easily in the PropertyChangedCallback hooked up from the DependencyProperty's PropertyMetadata.
public class BottomData : DependencyObject, INotifyPropertyChanged
{
public string Name { get; set; }
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(BottomData), new PropertyMetadata(0d,new PropertyChangedCallback(OnValueChanged)));
public double Value
{
get { return (double)this.GetValue(ValueProperty); }
set { base.SetValue(ValueProperty, value); }
}
public BottomData(string name, double value)
{
this.Name = name;
this.Value = value;
}
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
BottomData bd = d as BottomData;
bd.NotifyPropertyChanged("Value");
}
void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}

XAML Binding to a FrameworkElement in a UserControl

I got a custom UserControl (MyControl) with several properties (which works just fine). I want a new property that let the page using the UserControl "paste in" some content to be shown direct in the UserControl - ex. a Path. I have tried; ContentPresenter, ContentControl, StackPanel, with no luck...
MyControl.xaml
<ContentControl Grid.Column="0" Content="{Binding MyContent, ElementName=root}"></ContentControl>
MyControl.xaml.cs
public object MyContent
{
get { return (object)GetValue(MyContentProperty); }
set { SetValue(MyContentProperty, value); }
}
public static readonly DependencyProperty MyContentProperty =
DependencyProperty.Register("MyContent", typeof(object), typeof(MyControl), new PropertyMetadata(null));
SomePage.xml
<mycontrols:MyControl x:Name="FavoritesButton">
<mycontrols:MyControl.MyContent>
<Path Data="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z" Stretch="Uniform" Fill="#FFFFFFFF" Width="50" Height="50" Margin="30"></Path>
</mycontrols:MyControl.MyContent>
</mycontrols:MyControl>
I have the following which works really well. (While it is somewhat against the principles of MVVM ... I still like to dynamically handle my user controls in a single frame area of the main window)
My MainWindow.xaml:
<!-- Main Frame -->
<Grid Grid.Column="1" Margin="10" Name="MainWindowFrameContent">
<ItemsControl ItemsSource="{Binding Path=MainWindowFrameContent}" >
<!-- This controls the height automatically of the user control -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
My MainViewModel.cs:
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
using myProject.View;
using myProject.Models;
namespace myProject.ViewModel
{
public class MainViewModel : ObservableObject
{
public MainViewModel() { }
// This handles adding framework (UI) elements to the main window frame
ObservableCollection<FrameworkElement> _MainWindowFrameContent = new ObservableCollection<FrameworkElement>();
public ObservableCollection<FrameworkElement> MainWindowFrameContent
{
get { return _MainWindowFrameContent; }
set { _MainWindowFrameContent = value; RaisePropertyChangedEvent("MainWindowFrameContent"); }
}
}
}
MainViewModel.cs is a "public class MainViewModel : ObservableObject". This allows me to implement "RaisePropertyChangedEvent" so that the binding will successfully update when I change the value of "MainWindowFrameContent".
My ObservableObject.cs:
using System.ComponentModel;
namespace myProject.ViewModel
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then when I want to add an item to the MainWindowFrameContent, I simply do the following within my MainViewModel.cs:
void _AddNewUserControl()
{
myUserControl hControl = new myUserControl();
MainWindowFrameContent.Clear(); // Clear the frame before displaying new content
MainWindowFrameContent.Add(hControl);
}
Then you can create as many user controls as you would like. Each command you want to display can have its own void _AddNewUserControl type method in the VM and it will display to the main window. Again, its a bit contrary to the MVVM framework, but it keeps this pretty clean from a code base.
I got it working... The solution was as follows:
MyControl.xaml
<ContentControl Content="{Binding Shape, ElementName=root}" />
MyControl.xaml.cs
public Shape Shape
{
get { return (Shape)GetValue(ShapeProperty); }
set { SetValue(ShapeProperty, value); }
}
public static readonly DependencyProperty ShapeProperty =
DependencyProperty.Register("Shape", typeof(Shape), typeof(MyControl), new PropertyMetadata(null));
SomePage.xml
<mycontrols:MyControl>
<mycontrols:MyControl.Shape>
<Path Data="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z" Style="{StaticResource PathStyle}" />
</mycontrols:MyControl.Shape>
</mycontrols:MyControl>

Binding to User Control Properties in an Items Control

Can someone tell me why the way I am binding data here does not work? I was able to do this fine with using a GridView. I am not sure why the data is not being passed to the user control here, in addition it compiles fine and shows the user controls with there default text
Main.xaml.cs:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
namespace ItemsControlSample
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
/// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached. The Parameter
/// property is typically used to configure the page.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
PopulateData();
}
public class someKindaOfDataHolder
{
public string Text1;
public string Text2;
}
private void PopulateData()
{
List<someKindaOfDataHolder> dataAndStuff = new List<someKindaOfDataHolder>();
for (int i = 0; i < 5; ++i)
{
dataAndStuff.Add(new someKindaOfDataHolder()
{
Text1 = "data spot 1: " + i.ToString(),
Text2 = "data spot 2: " + (i + 2).ToString()
});
}
ListOfAUserControls.ItemsSource = dataAndStuff;
}
}
}
Main.xaml
<Page
x:Class="ItemsControlSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ItemsControlSample"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<ScrollViewer>
<ItemsControl x:Name="ListOfAUserControls" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:CustomUserControl DynamicText1Text="{Binding Text1}" DynamicText2Text="{Binding Text2}" Margin="0,0,10,0"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="100,46,-50,0"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
</Page>
CustomUserControl.xaml.cs:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236
namespace ItemsControlSample
{
public sealed partial class CustomUserControl : UserControl
{
public static readonly DependencyProperty DynamicText1Property =
DependencyProperty.Register("DynamicText1Text", typeof(string), typeof(CustomUserControl), new PropertyMetadata(null, OnDynamicText1PropertyChanged));
public string DynamicText1Text
{
get { return (string)GetValue(DynamicText1Property); }
set { SetValue(DynamicText1Property, value); }
}
public static readonly DependencyProperty DynamicText2Property =
DependencyProperty.Register("DynamicText2Text", typeof(string), typeof(CustomUserControl), new PropertyMetadata(null, OnDynamicText2PropertyChanged));
public string DynamicText2Text
{
get { return (string)GetValue(DynamicText2Property); }
set { SetValue(DynamicText2Property, value); }
}
public CustomUserControl()
{
this.InitializeComponent();
}
private static void OnDynamicText1PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var obj = d as CustomUserControl;
obj.DynamicText1.Text = e.NewValue.ToString();
}
private static void OnDynamicText2PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var obj = d as CustomUserControl;
obj.DynamicText2.Text = e.NewValue.ToString();
}
}
}
CustomUserControl.xaml:
<UserControl
x:Class="ItemsControlSample.CustomUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ItemsControlSample"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="800"
d:DesignWidth="800">
<Grid Background="Blue">
<Grid.RowDefinitions>
<RowDefinition Height="6*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<Image Source="ms-appx:///Assets/MSIcon.png" />
<StackPanel Grid.Row="1" Margin="25">
<TextBlock Text="Obay" FontSize="45" />
<TextBlock x:Name="DynamicText1" Text="DynamicText1" FontSize="35" />
<TextBlock x:Name="DynamicText2" Text="DynamicText2" FontSize="35" />
</StackPanel>
</Grid>
</UserControl>
Thanks!
You need to set the DataContext somewhere. Without it - your bindings have no context and they fail (look at the Output window in VS when debugging Debug builds to see binding errors). You are setting ItemsSource="{Binding}" where the binding has no context.
Then again - you are overriding the ItemsSource elsewhere with your "ListOfAUserControls.ItemsSource = dataAndStuff;" call, so that is likely not the issue causing your problem in the end. The DataContext of an item in an ItemsControl should be set automatically to an item in the ItemsSource collection.
Another problem that might block you there is that someKindaOfDataHolder.Text1 in your case is a field and it should be a property to work with bindings - try this instead:
public string Text1 { get; set; }

Categories