How to update all items shown by ItemsControl using a condition? - c#

In a UWP Project I have a UI which has an ItemsControl bound to a set of Team objects. There is a separate GameController object that has a CurrentTeam property which changes as the Game progresses. I want to be able to have a visual cue in the ItemTemplate for the Team that is the CurrentTeam. An example would be the Current Team's name gets underlined say. The Team objects do not have a reference to the GameController.
One way is to put a flag on each Team, say IsCurrentTeam and bind to that in the ItemTemplate. I don't particularly like this approach as it means when the CurrentTeam changes I've got to loop around all the Teams except the current one, to update their flags.
In WPF I think there might have been a solution using an ObjectDataProvider as it offers the ability to bind to a method, but since I'm in UWP this option is not available.
Does anyone know of a better approach to do this?

Ok, I've prepared an example that shows how this achievable. To work around limitations in UWP it uses a few techniques such as 'data context anchoring' and attached properties.
Here's my support classes, I assume they're somewhat similar to yours:
public class GameControllerViewModel : INotifyPropertyChanged
{
private Team _currentTeam;
public event PropertyChangedEventHandler PropertyChanged;
public GameControllerViewModel(IEnumerable<Team> teams)
{
Teams = teams;
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Team CurrentTeam
{
get { return _currentTeam; }
set
{
if (value != _currentTeam)
{
_currentTeam = value;
OnPropertyChanged();
}
}
}
public IEnumerable<Team> Teams { get; private set; }
}
public class Team
{
public string Name { get; set; }
}
And the code behind of the page:
public sealed partial class GamesPage : Page
{
public GamesPage()
{
this.InitializeComponent();
this.DataContext = new GameControllerViewModel(
new[]
{
new Team { Name = "Team A" },
new Team { Name = "Team B" },
new Team { Name = "Team C" },
new Team { Name = "Team D" }
}
);
}
}
As you can see, the constructor of the page instantiates a GameControllerViewModel with four teams and sets it as the data context of the page.
The page XAML is as follows:
<Page
x:Class="UniversalScratchApp.GamesPage" x:Name="View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UniversalScratchApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<local:BoolToFontWeightConverter x:Key="BoolToFontWeightConverter"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Current Team:" Margin="4" VerticalAlignment="Center"/>
<ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding Teams}" SelectedItem="{Binding CurrentTeam, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="4">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ItemsControl Grid.Row="1" Grid.ColumnSpan="2" ItemsSource="{Binding Teams}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" local:TeamProperties.CurrentTeam="{Binding ElementName=View, Path=DataContext.CurrentTeam}" local:TeamProperties.Team="{Binding}" FontWeight="{Binding Path=(local:TeamProperties.IsCurrentTeam), RelativeSource={RelativeSource Mode=Self}, Mode=OneWay, Converter={StaticResource BoolToFontWeightConverter}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Page>
In the DataTemplate of the ItemsControl you can see that I bind to a three custom attached properties; TeamProperties.CurrentTeam, TeamProperties.Team and TeamProperties.IsCurrentTeam. The attached properties are defined in the following class:
[Bindable]
public static class TeamProperties
{
private static void TeamPropertiesChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
Team team = GetTeam(sender);
Team currentTeam = GetCurrentTeam(sender);
if (team != null && currentTeam != null)
{
SetIsCurrentTeam(sender, team.Equals(currentTeam));
}
}
public static readonly DependencyProperty CurrentTeamProperty = DependencyProperty.RegisterAttached("CurrentTeam", typeof(Team), typeof(TeamProperties), new PropertyMetadata(null, TeamPropertiesChanged));
public static Team GetCurrentTeam(DependencyObject obj)
{
return (Team)obj.GetValue(CurrentTeamProperty);
}
public static void SetCurrentTeam(DependencyObject obj, Team value)
{
obj.SetValue(CurrentTeamProperty, value);
}
public static readonly DependencyProperty TeamProperty = DependencyProperty.RegisterAttached("Team", typeof(Team), typeof(TeamProperties), new PropertyMetadata(null, TeamPropertiesChanged));
public static Team GetTeam(DependencyObject obj)
{
return (Team)obj.GetValue(TeamProperty);
}
public static void SetTeam(DependencyObject obj, Team value)
{
obj.SetValue(TeamProperty, value);
}
public static readonly DependencyProperty IsCurrentTeamProperty = DependencyProperty.RegisterAttached("IsCurrentTeam", typeof(bool), typeof(TeamProperties), new PropertyMetadata(false));
public static bool GetIsCurrentTeam(DependencyObject obj)
{
return (bool)obj.GetValue(IsCurrentTeamProperty);
}
public static void SetIsCurrentTeam(DependencyObject obj, bool value)
{
obj.SetValue(IsCurrentTeamProperty, value);
}
}
To explain, the CurrentTeam and Team properties are set on the dependency object (the textblock) by the bindings. While the Team property can use the current datacontext, the CurrentTeam property must be bound to the 'outer' DataContext. It does this by specifying an x:Name="View" on the Page and using that to 'anchor' the datacontext so it can then be accessed by bindings using the ElementName=View part of the binding.
So, whenever either of these properties change, the IsCurrentTeam property is set on the same dependency object by the TeamPropertiesChanged callback. The IsCurrentTeam property then is bound to the FontWeight property (as it was easier than underlining) with the BoolToFontWeightConverter shown here:
public class BoolToFontWeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool)
{
return ((bool)value) ? FontWeights.ExtraBold : FontWeights.Normal;
}
else
{
return DependencyProperty.UnsetValue;
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotSupportedException();
}
}
Now, when a team is selected in the top combobox (a proxy for whatever mechanism you use to change teams) the appropriate team in the ItemsControl will be displayed in bold.
Works nicely for me. Hope it helps.

In WPF my preferred option would be a DataTrigger, setting the underline property when the DataContext {Binding} of an item equals the contents of CurrentTeam on the parent (use an elementname to refer to that).
As UWP doesn't support Trigger, you can use a DataTriggerBehaviour like in this post.

Related

How to bind the sub-property of an item in a listView?

I am stuck while trying to bind a property to another property.
The 2 properties are:
in a richTextBox, the 'Content' dependency property
in a listView, the selectedItem is a Book, and the selected book has a string property named "End".
I have to use 2 converters to transform the Content to string and the string to Content, so I can't use the TwoWay binding mode.
With this code:
<controls:RichEditControl
x:Name="richEditControl1"
BarManager="{Binding ElementName=barManager1, Mode=OneTime}"
HorizontalRulerVisibility="Collapsed"
VerticalRulerVisibility="Collapsed"
Content="{Binding ElementName=listBoxBooks, Path=SelectedItem.End, Converter={StaticResource PlainContentConverter}, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/>
there should be half of the binding, but I can't figure out how to implement the second half, I mean from listView -> (Book)selectedItem -> End to the Content property.
I tried something like this:
Binding myBinding = new Binding("Content");
myBinding.Source = richEditControl1;
myBinding.Mode = BindingMode.OneWay;
listBoxBooks.SetBinding(ListView.SelectedItemProperty, myBinding);
but this is wrong as I don't want to bind the entire SelectedItemProperty but only its 'End' property. (the selectedItem is a 'Book' class).
thank you.
EDIT1
I changed the code-behind following the advice given in commentary, but without any success.
the code is:
Binding myBinding = new Binding("End");
myBinding.Source = (Book)listBoxBooks.SelectedItem;
myBinding.Mode = BindingMode.OneWay;
richEditControl1.SetBinding(RichEditControl.ContentProperty, myBinding);
(in fact it invert the direction of the binding, but I think the first direction was wrong).
thank you for your answer, but I just found a solution : in fact, I did not tell you that I tried with a binding mode equal to TwoWay, I told you it wasn't possible. But I was wrong, a TwoWay is compatibvle with a converter as the converter contains two methods for each direction of conversion. It was possible, with this code :
Content="{Binding ElementName=listBoxBooks, Path=SelectedItem.End,Converter={StaticResource PlainToContentConverter},UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
but the formatting was lost. I thought that was caused by the lack of the second converter (in the other direction) but as I just pointed out, I was wrong. The above code does not keep formatting because it is simply not saving it in plain text! Replacing the converter by an HtmlToContentConverter does the job!
thank you anyway for the time you spent!
After the update I think I understand what you are trying to do. So my understanding is that you have a list of books and on selection of a book you just want to update your custom control to reflect.
If there is no specific reason for you binding via code, you could use the below technique.
( I made a quick demo on my end for example)
Step 1 : Setup XAML to bind directly to Listbox.SelectedItem.
<Window x:Class="TestWPF.MainWindow"
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:TestWPF"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="800">
<Window.Resources>
<local:DummyConverter x:Key="DummyConverter"/>
</Window.Resources>
<Grid Margin="15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox x:Name="MyListOfBooks" ItemsSource="{Binding Path=BookCollection, Mode=OneWay}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Title, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="{Binding Path=End, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, StringFormat='dddd, dd MMMM yyyy'}" Margin="30,0,0,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!--Replace with custom control, make sure your custom control has a dependancy property for this binding-->
<TextBox x:Name="MyTextBox" Grid.Column="2" Text="{Binding Path=SelectedItem.Title, ElementName=MyListOfBooks, Converter={StaticResource DummyConverter}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="10"/>
</Grid>
</Window>
Step 2: Here is my demo code to assist.
namespace TestWPF
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
MainViewModel model;
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
model = new MainViewModel();
model.Load();
this.DataContext = model;
}
}
public class MainViewModel
{
public ObservableCollection<Book> BookCollection { get; set; }
public void Load()
{
BookCollection = new ObservableCollection<Book>
{
new Book() { Title = "Book One", End = DateTime.Now },
new Book() { Title = "Book Two", End = DateTime.Now.AddDays(10) },
new Book() { Title = "Book Three", End = DateTime.Now.AddDays(2) }
};
}
}
public class Book : INotifyPropertyChanged
{
private string title;
private DateTime end;
public string Title
{
get { return title; }
set
{
title = value;
NotifyPropertyChanged();
}
}
public DateTime End
{
get { return end; }
set
{
end = value;
NotifyPropertyChanged();
}
}
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class DummyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return null;
System.Text.ASCIIEncoding encoding = new ASCIIEncoding();
return string.Join("-", encoding.GetBytes((value as string) ?? string.Empty));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return null;
string val = (value as string);
var array = val.Split('-');
Byte[] byteArray = new Byte[array.Length];
for (int i = 0; i < array.Length; i++)
{
Byte.TryParse(array[i], out byte x);
byteArray[i] = x;
}
return Encoding.ASCII.GetString(byteArray);
}
}
}
If you're still having trouble with the binding or If I misunderstood the issue, please let me know. Also provide the code for your custom control.

How can I assign DataGrids to a user control in WPF at the point of use?

I am trying to define a user control for the typical dual list situation (where there are two lists of items side by side and button controls to cause selected items from one to be transferred to the other). I am not very proficient at WPF -- most of what I've learned has been bits and pieces through sites like this. I have learned that I can create custom dependency properties for the control so that I can defer binding of items in the control (buttons, textboxes, etc.) until the control is actually used which is great. However, for my control I am going to have the two lists (probably DataGrids since most of my code, to date, has involved them) but they will require a lot more than binding so what I would like to do is something like this:
<MyUserControl . . . .>
<DataGrid . . . .>
<DataGrid . . . .>
</MyUserControl>
But I have no idea how to make that work. I thought there might be some way I could use ContentControls as a stand-in for the DataGrids and then somehow link the datagrids back to the contentcontrols in the usercontrol but I don't really understand Contentcontrols and none of the examples I found using them seemed to apply at all to what I want to do.
Can anyone point me in the right direction on this? Thank you.
I did some more research and found a promising approach, here:
How to add controls dynamically to a UserControl through user's XAML?
I did a small proof-of-concept project and it worked great so I thought I would share it here. It uses Galasoft's MVVM Light framework.
I created a user control with a textblock, a ContentControl, and a button:
<UserControl x:Class="DataGridInUserControlDemo.UserControls.DGPlusUC"
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:ignore="http://www.galasoft.ch/ignore"
mc:Ignorable="d ignore"
x:Name="ControlRoot">
<Grid DataContext="{Binding ElementName=ControlRoot}" Margin="10, 10, 10, 10" MinHeight="300" MinWidth="300">
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="5*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Path=BannerText}" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="20"/>
<ContentControl Grid.Row="1" Content="{Binding Path=InnerContent}" />
<Button Grid.Row="2" x:Name="DemoButton" Content="{Binding Path=ButtonContent}" Command="{Binding Path=ButtonCommand}" Width="75" Height="25" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</UserControl>
The attributes I wish to bind external to the UserControl are themselves bound to custom DependencyProperty(s).
This is the code-behind for the user control containing the DependencyProperty definitions:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace DataGridInUserControlDemo.UserControls
{
public partial class DGPlusUC : UserControl
{
public DGPlusUC()
{
InitializeComponent();
}
public const string BannerTextPropertyName = "BannerText";
public string BannerText
{
get
{
return (string)GetValue(BannerTextProperty);
}
set
{
SetValue(BannerTextProperty, value);
}
}
public static readonly DependencyProperty BannerTextProperty = DependencyProperty.Register(
BannerTextPropertyName,
typeof(string),
typeof(DGPlusUC));
public const string ButtonContentPropertyName = "ButtonContent";
public string ButtonContent
{
get
{
return (string)GetValue(ButtonContentProperty);
}
set
{
SetValue(ButtonContentProperty, value);
}
}
public static readonly DependencyProperty ButtonContentProperty = DependencyProperty.Register(
ButtonContentPropertyName,
typeof(string),
typeof(DGPlusUC));
public const string ButtonCommandPropertyName = "ButtonCommand";
public ICommand ButtonCommand
{
get
{
return (ICommand)GetValue(ButtonCommandProperty);
}
set
{
SetValue(ButtonCommandProperty, value);
}
}
public static readonly DependencyProperty ButtonCommandProperty = DependencyProperty.Register(
ButtonCommandPropertyName,
typeof(ICommand),
typeof(DGPlusUC));
public const string InnerContentPropertyName = "InnerContent";
public UIElement InnerContent
{
get
{
return (UIElement)GetValue(InnerContentProperty);
}
set
{
SetValue(InnerContentProperty, value);
}
}
public static readonly DependencyProperty InnerContentProperty = DependencyProperty.Register(
InnerContentPropertyName,
typeof(UIElement),
typeof(DGPlusUC));
}
}
This is the XAML for the main window:
<Window x:Class="DataGridInUserControlDemo.MainWindow"
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:ignore="http://www.galasoft.ch/ignore"
xmlns:demo="clr-namespace:DataGridInUserControlDemo.UserControls"
mc:Ignorable="d ignore"
Height="400"
Width="400"
Title="MVVM Light Application"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skins/MainSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<demo:DGPlusUC BannerText="{Binding UCTitle}" ButtonContent="{Binding ButtonText}" ButtonCommand="{Binding ButtonCommand}" HorizontalAlignment="Center" VerticalAlignment="Center">
<demo:DGPlusUC.InnerContent>
<Grid DataContext="{Binding Main, Source={StaticResource Locator}}">
<DataGrid ItemsSource="{Binding Path=DataItems}" AutoGenerateColumns="True" />
</Grid>
</demo:DGPlusUC.InnerContent>
</demo:DGPlusUC>
</Grid>
</Window>
The custom DependencyProperty(s) are used as tags in the control to bind to the properties in the ViewModel.
Note the bracketed demo:DGPlusUC.InnerContent -- this is where the replacement code for the ContentControl goes. The embedded UserControl inherits the Window's datacontext but for some reason this section did not, so, after experimenting with it a bit, I just threw up my hands and declared the DataContext explicitly.
And here is the ViewModel code:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using DataGridInUserControlDemo.Model;
using System.Windows.Input;
using System.Collections.ObjectModel;
namespace DataGridInUserControlDemo.ViewModel
{
public class MainViewModel : ViewModelBase
{
private readonly IDataModel theModel;
private ObservableCollection<DataItem> _DataItems;
public ObservableCollection<DataItem> DataItems
{
get
{
return _DataItems;
}
set
{
if (_DataItems == value)
{
return;
}
var oldValue = _DataItems;
_DataItems = value;
RaisePropertyChanged(() => DataItems, oldValue, value, true);
}
}
private string _ButtonText = "First";
public string ButtonText
{
get
{
return this._ButtonText;
}
set
{
if (this._ButtonText == value)
{
return;
}
var oldValue = this._ButtonText;
this._ButtonText = value;
RaisePropertyChanged(() => ButtonText, oldValue, value, true);
}
}
private string _UCTitle = string.Empty;
public string UCTitle
{
get
{
return this._UCTitle;
}
set
{
if (this._UCTitle == value)
{
return;
}
var oldValue = this._UCTitle;
this._UCTitle = value;
RaisePropertyChanged(() => UCTitle, oldValue, value, true);
}
}
private ICommand _ButtonCommand;
public ICommand ButtonCommand
{
get
{
return this._ButtonCommand;
}
set
{
if (this._ButtonCommand == value)
{
return;
}
var oldValue = this._ButtonCommand;
this._ButtonCommand = value;
RaisePropertyChanged(() => ButtonCommand, oldValue, value, true);
}
}
public MainViewModel(IDataModel model)
{
this.theModel = model;
this._UCTitle = "DataGrid in User Control Demo";
this._DataItems = new ObservableCollection<DataItem>(this.theModel.SomeData);
this._ButtonCommand = new RelayCommand(this.ButtonCmd, () => { return true; }) ;
}
private void ButtonCmd()
{
if (this.ButtonText == "First")
{
this.ButtonText = "Second";
}
else
{
this.ButtonText = "First";
}
}
}
}
Finally, here are the results:
DataGrid in UserControl Demo

Binding to a UserControl in WPF using C#

Preface
The control I am giving as an example is an sample work for a larger project. I have already had some help from the community on Stackoverflow ironing out some of the finer points of bindings within the control. The surprise has been that I am having an issue binding in the control's hosting form.
I have read and researched around DependencyProperty for a lot of hours. I was not a WPF developer at the start of the year but I am now covering the role because of a death in the business, and I accept this is a big hill to climb.
The question is what is missing here in my:
The hosting form's XAML code
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:AControl="clr-namespace:AControl;assembly=AControl" x:Class="DependencySampler.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<AControl:UserControl1 x:Name="cboBob" HorizontalAlignment="Left" Margin="100,118,0,0" VerticalAlignment="Top" Width="200" Height="29" SelectedColor="{Binding Path=BeSelected, Mode=OneWayToSource}"/>
</Grid>
The code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new viewModelBinding();
BeSelected = new modelMain("Yellow", "#FFFFE0");
}
public modelMain BeSelected
{
get { return ((viewModelBinding)DataContext).Selected; }
set { ((viewModelBinding)DataContext).Selected = value; }
}
}
The ViewModel
public class viewModelBinding :ViewModelBase
{
modelMain sel = new modelMain("Red", "#FF0000");
public modelMain Selected
{
get { return sel; }
set { SetProperty(ref this.sel, value, "Selected"); }
}
}
The next section is the control itself.
The Model
public class modelMain:ViewModelBase
{
public modelMain(string colName, string hexval)
{
ColorName = colName;
HexValue = hexval;
}
string colorName;
public string ColorName
{
get { return colorName; }
set { SetProperty(ref this.colorName, value, "ColorName"); }
}
string hexValue;
public string HexValue
{
get { return hexValue; }
set { SetProperty(ref this.hexValue, value, "HexValue"); }
}
}
The ViewModel
public class viewModelMain:ViewModelBase
{
ObservableCollection<modelMain> val = new ObservableCollection<modelMain>();
public ObservableCollection<modelMain> ColorsList
{
get { return val; }
set { SetProperty(ref this.val, value, "Colors"); }
}
modelMain selectedColor;
public modelMain SelectedColour
{
get{return selectedColor;}
set { SetProperty(ref this.selectedColor, value, "SelectedColour"); }
}
public void SetCurrentColor(modelMain col)
{
SelectedColour = this.val.Where(x => x.ColorName == col.ColorName).FirstOrDefault();
}
public viewModelMain()
{
val.Add(new modelMain("Red", "#FF0000"));
val.Add(new modelMain("Blue", "#0000FF"));
val.Add(new modelMain("Green", "#008000"));
val.Add(new modelMain("Yellow", "#FFFFE0"));
SelectedColour = new modelMain("Blue", "#0000FF");
}
}
The UserControl XAML
<UserControl x:Class="AControl.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"
d:DesignHeight="32" d:DesignWidth="190">
<Grid>
<ComboBox x:Name="cboValue"
SelectionChanged="cboValue_SelectionChanged"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding ColorList, RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedValue="{Binding SelectedColor, RelativeSource={RelativeSource AncestorType=UserControl}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Width="10"
Height="10"
Margin="5"
Background="{Binding ColorName}"/>
<TextBlock Width="35"
Height="15"
Margin="5"
Text="{Binding ColorName}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
The UserControl Code behind
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
ObservableCollection<modelMain> colorList = new viewModelMain().ColorsList;
public ObservableCollection<modelMain> ColorList
{
get { return colorList; }
set { colorList = value; }
}
public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register(
"SelectedColor",
typeof(modelMain),
typeof(UserControl1),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnSelectedColorChanged),
new CoerceValueCallback(CoerceSelectedColorCallback)));
private static void OnSelectedColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UserControl1 uc = (UserControl1)d;
uc.SelectedColor = (modelMain)e.NewValue;
}
private static object CoerceSelectedColorCallback(DependencyObject d, object value)
{
return (modelMain)value;
}
public modelMain SelectedColor
{
get { return (modelMain)GetValue(SelectedColorProperty); }
set { SetValue(SelectedColorProperty, value); }
}
private void cboValue_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var dat = sender as ComboBox;
SelectedColor = (modelMain)dat.SelectedValue;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
//var dat = sender as ComboBox;
////SelectedColor = (modelMain)dat.SelectedValue;
//SelectedColor = (modelMain)this.SelectedColor;
}
}
Please note that in the code behind there is unused code but within the sample I have used then for placing break points
I understand that no DataContext should exist in the UserControl because it precludes one in the hosting form.
The Question
I was expecting the this line would be sufficient in the hosting form.
<AControl:UserControl1 x:Name="cboBob" HorizontalAlignment="Left" Margin="100,118,0,0" VerticalAlignment="Top" Width="200" Height="29" SelectedColor="{Binding Path=BeSelected, Mode=OneWayToSource}"/>
But it does not seem to do what I expected. I can see the BeSelected be initialised and it is holding a value but when the form loads I am expecting the colour yellow to enter the UserControl's and set DependencyProperty SelectedColor. This is not happening why and how can I get it to happen?
To get you example working, do the following (for the most part, implement what the commenters said):
The hosting form's XAML code
<AControl:UserControl1 x:Name="cboBob" HorizontalAlignment="Left" Margin="100,118,0,0" VerticalAlignment="Top" Width="200" Height="29" SelectedColor="{Binding Path=BeSelected, RelativeSource={RelativeSource AncestorType=Window}}}"/>
The Mode doesn't really matter since MainWindow doesn't implement INPC nor does it ever know when ((viewModelBinding)DataContext).Selected (and therefor, BeSelected) is changed. Actually, like Joe stated, OneWayToSource doesn't work... RelativeSource was needed because BeSelected is a property of the MainWindow - not MainWindow's DataContext.
modelMain
modelMain needs to implement IEquatable (like Janne commented). Why? Because BeSelected = new modelMain(...) creates a new modelMain which is not one of the items in the ComboBox's ItemsSource (ColorList). The new object may have the same property values as one of the items but that doesn't make them equal (different objects = different address in memory). IEquatable gives you the opportunity to override that.
public class modelMain : ViewModelBase, IEquatable<modelMain>
{
...
public bool Equals(modelMain other)
{
return (HexValue == other.HexValue);
}
}
viewModelMain's ColorList's setter is calling SetProperty with property name "Colors" when it should be "ColorsList". It's not being used so it doesn't stop your example from working but it's still wrong.

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;
}

Problem with DataBinding in mvvm

I have big problem with databinding.
I cant bind data to children control. I'm really newbie in MVVM and I spend a lot of hours at this example and I have no idea what is wrong with this Code.
Little explanation:
I have MainWindow. It has UserControl to display list of todo.
I want to set my MyWindow class ParentViewModel as DataContext.
DataContext has TodoItemModelView as subdatacontext which must be datacontext of UserControlTodoItems.
<Window x:Class="Repo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Repo="clr-namespace:Repo" Title="Window1" Height="300" Width="300">
<Window.Resources>
<Repo:ParentViewModel x:Key="parentVM"/>
</Window.Resources>
<Window.DataContext>
<StaticResourceExtension ResourceKey="parentVM"/>
</Window.DataContext>
<Grid>
<Repo:UserControlTodoItems DataContext="{Binding Path=todoItemModelView}">
</Repo:UserControlTodoItems>
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
class ParentViewModel
{
public TodoItemModelView todoItemModelView { get; set; }
public ParentViewModel()
{
this.todoItemModelView=new TodoItemModelView();
}
}
public class TodoItemModelView
{
public ObservableCollection<TodoItem> todoItems { get; set; }
public TodoItemModelView()
{
ObservableCollection<TodoItem> loadedTodoItems = new ObservableCollection<TodoItem>();
loadedTodoItems.Add(new TodoItem() { Code = "10", ObjectCode = "DE", ObjectType = ObjectType.Country, Status = TodoItemStatus.InProgress, Type = TodoItemType.CollectPhotos });
loadedTodoItems.Add(new TodoItem() { Code = "11", ObjectCode = "DE", ObjectType = ObjectType.Country, Status = TodoItemStatus.Todo, Type = TodoItemType.DescribeOjbect });
loadedTodoItems.Add(new TodoItem() { Code = "12", ObjectCode = "DE", ObjectType = ObjectType.Country, Status = TodoItemStatus.Accomplshed, Type = TodoItemType.CollectVideos });
todoItems = loadedTodoItems;
}
}
<UserControl x:Class="Repo.UserControlTodoItems"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Repo="clr-namespace:Repo" Height="auto" Width="auto">
<UserControl.Resources>
<Repo:TodoItemStatusConverter x:Key="TodoItemStatusConverter"/>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=todoItems}" Name="lbTasks">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Status, Converter={StaticResource TodoItemStatusConverter}}"/>
<TextBlock Text="{Binding Path=Type}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
public UserControlTodoItems()
{
InitializeComponent();
}
I correct this.
I must add one question:
is there any simple way to inform parentmodel, of change checkbox at listbox?
this is a converter:
public class TodoItemStatusConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
TodoItemStatus todoItemStatus = (TodoItemStatus)value;
if (todoItemStatus == TodoItemStatus.Accomplshed)
{
return true;
}
else
{
return false;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool) value)
{
return TodoItemStatus.Accomplshed;
}
else
{
return TodoItemStatus.InProgress;
}
}
This is class TodoItem:
public class TodoItem
{
public TodoItemType Type { get; set; }
public TodoItemStatus Status { get; set; }
public string Code { get; set; }
public string ObjectCode { get; set; }
public ObjectType ObjectType { get; set; }
}
Why is the binding for your "lbTasks" Listbox just "{Binding}" and not "{Binding Path=todoItems}"
I'm really taking a quick glance at your code here.. you seem to be passing the todoItemModelView as a DataContext properly, but never inform the listbox where in that data context it will find its items.
You may also want to use an ObservableCollection for the list in the VM so you can add and remove todo's in a way the GUI can respond to
<CheckBox IsChecked="{Binding Path=Status, Converter={StaticResource TodoItemStatusConverter}}"/>
This implies there is a property on ToDoItemViewModel called Status - but there isn't! Rethink your ToDoItemVm class to just be a wrapper for a toDoItem (ie, public ToDoItemVm(ToDoItem model) and get that array of items into the PArentVm (do use ObservableCollection and bind it to the list box. Also add a SelectedToDoItem property on the ParentVm. So your binding for the list box includes something like
ItemsSource="{Binding ToDoTems}"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedToDoItem, Mode=TwoWay}"
Then expose that Status property on your ToDoItemVm, have the class implement INPC, and raise PropertyChanged in the setter.
It may take some work to sort it out, so feel free to ask more questions as you go. The converter idea is fine.
HTH,
Berryl

Categories