Problem with DataBinding in mvvm - c#

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

Related

UWP Binding a specific property of a hierachical object

I want to access some values at the "root" of my main object (which is an object with a hierachical structure) inside some level of my datagrid that have another datacontext. At the moment it looks like this:
see image
As you can see on the screenshot with the textblocks above the grid I can access these properties outside, but in the grid I can't and I get a binding error.
The first level of my grid is binded with value1, the second with value2.
The errors for first level is:
Error: BindingExpression path error: 'TestValue' property not found on 'ProjectSolution.Core.Models.CaseTask'. BindingExpression: Path='TestValue' DataItem='ProjectSolution.Core.Models.CaseTask'; target element is 'Windows.UI.Xaml.Controls.TextBlock' (Name='null'); target property is 'Text' (type 'String')
I have the same error for the second level of my grid with the other property.
Here is the code I have simplified to highlight my problem:
<DataTemplate x:Key="templateCell1" x:DataType="models:Case">
<Grid x:Name="ItemCellGrid">
<TextBlock Text="{Binding TestValue}"></TextBlock>
</Grid>
</DataTemplate>
<DataTemplate x:Key="templateCell2" x:DataType="models:Case">
<Grid x:Name="ItemCellGrid">
<TextBlock Text="{Binding TestValue2}"></TextBlock>
</Grid>
</DataTemplate>
<controls:DataGrid
x:Name="dataGrid"
ItemsSource="{x:Bind ViewModel.SingleCase.CaseTasks}">
<controls:DataGrid.Columns>
<controls:DataGridTextColumn Header="Tasks" Binding="{Binding Name}" FontWeight="SemiBold" />
</controls:DataGrid.Columns>
<controls:DataGrid.RowDetailsTemplate>
<DataTemplate>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" >
<Grid>
<controls:DataGrid
x:Name="rowDatagrid"
ItemsSource="{Binding CaseTasksDetails}">
<controls:DataGrid.Columns>
<controls:DataGridTextColumn Header="Sub-Tasks" Binding="{Binding Name}" FontWeight="SemiBold" />
<controls:DataGridTemplateColumn Header="Test" CellTemplate="{StaticResource templateCell2}" />
</controls:DataGrid.Columns>
</controls:DataGrid>
</Grid>
</StackPanel>
<DataTemplate>
</controls:DataGrid.RowDetailsTemplate>
</controls:DataGrid>
My object:
public class Case
{
public string Name { get; set; }
public ObservableCollection<CaseTask> CaseTasks { get; set; }
public string TestValue { get; set; }
public string TestValue2 { get; set; }
public Case()
{
CaseTasks = new ObservableCollection<CaseTask>();
CaseTasks.Add(new CaseTask() { Name = "Level2_1" });
CaseTasks.Add(new CaseTask() { Name = "Level2_2" });
CaseTasks.Add(new CaseTask() { Name = "Level2_3" });
}
}
public class CaseTask
{
public string Name { get; set; }
public ObservableCollection<CaseTaskDetail> CaseTasksDetails { get; set; }
public CaseTask()
{
CaseTasksDetails = new ObservableCollection<CaseTaskDetail>();
CaseTasksDetails.Add(new CaseTaskDetail() { Name = "Level3_1" });
CaseTasksDetails.Add(new CaseTaskDetail() { Name = "Level3_2" });
CaseTasksDetails.Add(new CaseTaskDetail() { Name = "Level3_3" });
}
}
public class CaseTaskDetail
{
public string Name { get; set; }
}
My viewmodel:
public class CaseDetailViewModel
{
private Case _case;
public Case SingleCase
{
get { return _case; }
set {_case =value; }
}
public CaseDetailViewModel()
{
_case = new Case();
_case.TestValue = "Value1";
_case.TestValue2 = "Value2";
}
Of course one of the simplest ways would be to propagate the properties in the child objects, but this would create a lot of redundancy in my data. I am open to any suggestions.
You could save the value of TestValue2 which you want to access inside some level of the DataGrid as a static value of MainPage, and add DataGrid.LoadingRowDetails event handler to change the value of TestValue based on the selected row in the first level of DataGrid. And you could show the value by a data binding with a Converter, like this:
MainPage.xaml.cs
public static string testString = "";
……
private void dataGrid_LoadingRowDetails(object sender, DataGridRowDetailsEventArgs e)
{
int index = dataGrid.SelectedIndex;
testString = (index+2).ToString()+"th row: "+ ViewModel.SingleCase.TestValue2;
}
Converter class
public class DateFormatter : IValueConverter
{
// This converts the DateTime object to the string to display.
public object Convert(object value, Type targetType,
object parameter, string language)
{
return MainPage.testString;
}
// No need to implement converting back on a one-way binding
public object ConvertBack(object value, Type targetType,
object parameter, string language)
{
throw new NotImplementedException();
}
}
MainPage.xaml
<Page.Resources>
<local:DateFormatter x:Key="MyConverter"/>
……
</Page.Resources>
//Use the Converter in a column inside the second level of DataGrid
<controls:DataGridTextColumn Width="120" Header="Test" Binding="{Binding Converter={StaticResource MyConverter}}"/>

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.

UWP Binding to Visibility property of a FontIcon

I'm trying to bind the Visibility property of a FontIcon to an enum property in my view model using a converter, but for some reason it throws an exception
Unable to cast object of type 'Windows.UI.Xaml.Controls.FontIcon' to type
'Windows.UI.Xaml.Data.Binding'
What I want to achieve is that depending on the current value of CurrentSortOrder hide or show an icon inside the MenuFlyoutItem
View model code:
public class TestViewModel : ViewModelBase
{
private TaskSortType _currentTaskSortOrder = TaskSortType.BY_NAME_ASC;
public TaskSortType CurrentSortOrder
{
get => _currentTaskSortOrder;
set => Set(ref _currentTaskSortOrder, value);
}
}
View:
<Page
x:Class="UWPTests.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:UWPTests.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:UWPTests"
xmlns:localModels="using:UWPTests.Models"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
DataContext="{x:Bind ViewModel}"
mc:Ignorable="d">
<Page.Resources>
<converters:TaskSortTypeToVisibilityConverter x:Key="TaskSortTypeToVisibilityConverter" />
</Page.Resources>
<Grid>
<AppBarButton Icon="Sort" Label="Sort">
<AppBarButton.Flyout>
<MenuFlyout>
<MenuFlyoutSubItem Text="By name">
<MenuFlyoutItem Text="Asc">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="" Visibility="{Binding CurrentSortOrder, Mode=OneWay, Converter={StaticResource TaskSortTypeToVisibilityConverter}, ConverterParameter={x:Bind localModels:TaskSortType.BY_NAME_ASC}}" />
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem Text="Desc">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="" Visibility="Collapsed" />
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
</MenuFlyoutSubItem>
</MenuFlyout>
</AppBarButton.Flyout>
</AppBarButton>
</Grid>
Converter:
public class TaskSortTypeToVisibilityConverter : IValueConverter
{
public Visibility OnTrue { get; set; }
public Visibility OnFalse { get; set; }
public TaskSortTypeToVisibilityConverter()
{
OnFalse = Visibility.Collapsed;
OnTrue = Visibility.Visible;
}
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is null || parameter is null)
return Visibility.Collapsed;
var currentOrder = (TaskSortType)value;
var targetOrder = (TaskSortType)parameter;
return currentOrder == targetOrder ? OnTrue : OnFalse;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is Visibility == false)
return DependencyProperty.UnsetValue;
if ((Visibility)value == OnTrue)
return true;
else
return false;
}
}
Any help would be appreciated
Edit:
I get the exception here: this.InitializeComponent();
public sealed partial class MainPage : Page
{
public TestViewModel ViewModel { get; set; }
public MainPage()
{
ViewModel = new TestViewModel();
this.InitializeComponent();
}
}
Edit 2:
public enum TaskSortType
{
BY_NAME_ASC = 0,
BY_NAME_DESC = 1,
BY_UPDATED_DATE_ASC = 2,
BY_UPDATED_DATE_DESC = 3,
}
It seems that i cant use x:Bind directly in the ConverterParameter .. So i ended with the following:
I added in my page resources:
<localModels:TaskSortType x:Key="TaskSortByNameAsc">BY_NAME_ASC</localModels:TaskSortType>
<localModels:TaskSortType x:Key="TaskSortByNameDesc">BY_NAME_DESC</localModels:TaskSortType>
<localModels:TaskSortType x:Key="TaskSortByUpdatedDateAsc">BY_UPDATED_DATE_ASC</localModels:TaskSortType>
<localModels:TaskSortType x:Key="TaskSortByUpdatedDateDesc">BY_UPDATED_DATE_DESC</localModels:TaskSortType>
And then i replaced the ConverterParameter binding with the following:
<FontIcon Glyph="" Visibility="{Binding CurrentSortOrder, Mode=OneWay, Converter={StaticResource TaskSortTypeToVisibilityConverter}, ConverterParameter={StaticResource BY_NAME_ASC}}" />
Another workaround would be to pass the corresponding value in the ConverterParameter, for example ConverterParameter=0 or ConverterParameter="BY_NAME_ASC"and the cast that parameter to the corresponding enum value

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

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.

Dynamically create TextBoxes and bind them to a list

I am trying to implement a create mask, on which a user can create a new technology with different versions (e.g: .NET with versions 4.5.2 and 4.6 etc.). For that I want the user to be able to dynamically add text boxes for additional versions.
A requirement for this is to use the MVVM Pattern, which i'm fairly new to. I created the following classes for this:
Entitiy Classes (using Entity Framework)
public class Tech : EntityBase
{
// Properties
public string TechName { get; set; }
// Navigation Properties
public virtual ICollection<TechVersion> TechVersions{ get; set; }
}
public class TechVersion : EntityBase
{
// Properties
public string VersionNumber { get; set; }
// Foreign Keys
//public int TechId{ get; set; }
// Navigation Properties
public virtual Tech Tech{ get; set; }
}
Technology View Model
public class TechnologyUpdateViewModel : ObservableObject
{
private string _technologyName;
public string TechnologyName
{
get
{
return _technologyName;
}
set
{
if (_technologyName != value)
{
_technologyName = value;
OnPropertyChanged("TechnologyName");
}
}
}
private ICommand _add;
public ICommand Add
{
get
{
if (_add == null)
{
_add = new RelayCommand(e => CreateTechnology());
}
return _add;
}
set
{
if (_add != value)
{
_add = value;
OnPropertyChanged("Add");
}
}
}
private void CreateTechnology()
{
// Add new Technology from ViewModel Properties
}
}
I am however having trouble with the XAML Code. Binding the Technology Name and adding a new Technology with just the name works fine (Textbox + Button).
But how do I get the dynamically created Textboxes and bind them to the ViewModel?
I've tried the approach I found here
For this I added public ObservableCollection<string> Versions { get; set; }
But most solutions try to create controls from an already filled list. I am trying to create text boxes and bind them to an empty list.
TechnologyAdd.xaml
<UserControl x:Class="Presentation.Views.TechnologyAddOrEdit"
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:local="clr-namespace:Presentation.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<StackPanel MaxWidth="250">
<DockPanel>
<TextBlock>Name: </TextBlock>
<TextBox Text="{Binding TechnologyName}" Margin="10,0,0,0" />
</DockPanel>
<!-- Approach from the Link -->
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<ItemsControl ItemsSource="{Binding Versions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Save" Command="{Binding Add}" />
</StackPanel>
</StackPanel>
Maybe the whole MVVM thing confuses me too much but I can't seem to find a solution for this.
I would be happy about any information on how to approach this correctly.

Categories