Incorrect AlternationIndex when adding to an empty collection by entering a new DataGrid row - wpf

A simple example to reproduce the problem:
namespace IncorrectAlternativeIndex
{
/// <summary>A simple class to demonstrate the problem</summary>
public class Point2D
{
public double X { get; set; }
public double Y { get; set; }
}
}
namespace IncorrectAlternativeIndex
{
/// <summary>A simple ViewModel to demonstrate the problem</summary>
public class PointsViewModel : ViewModelBase
{
public ObservableCollection<Point2D> Points { get; } = new();
public RelayCommand ClearPoints => GetCommand(Points.Clear);
/// <summary>This command should not exist.
/// I brought it only to demonstrate the problem. </summary>
public RelayCommand Refresh => GetCommand<CollectionView>(cv => cv.Refresh());
}
}
<Window x:Class="IncorrectAlternativeIndex.CheckAlternativeIndexWindow"
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:IncorrectAlternativeIndex"
xmlns:sys="clr-namespace:System;assembly=netstandard"
mc:Ignorable="d"
Title="CheckAlternativeIndexWindow" Height="450" Width="800"
DataContext="{DynamicResource vm}">
<Window.Resources>
<local:PointsViewModel x:Key="vm"/>
</Window.Resources>
<UniformGrid Rows="1">
<DataGrid x:Name="dGrid" ItemsSource="{Binding Points}"
AlternationCount="{x:Static sys:Int32.MaxValue}">
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=(ItemsControl.AlternationIndex),
RelativeSource={RelativeSource FindAncestor,
AncestorType=DataGridRow}}"/>
</DataTemplate>
</DataGrid.RowHeaderTemplate>
</DataGrid>
<UniformGrid Columns="1">
<Button Content="Clear" Margin="50" Command="{Binding ClearPoints}"/>
<Button Content="Refresh" Margin="50" Command="{Binding Refresh}"
CommandParameter="{Binding Items, ElementName=dGrid}"/>
</UniformGrid>
</UniformGrid>
</Window>
When adding elements to an empty collection by typing into a new row directly in the DataGrid, the AlternationIndex value starts at AlternationCount - 1.
If do a Refresh of the collection view, the correct AlternationIndex calculation is restored.
After cleaning the collection, everything is repeated in the same way.
Is there a way to fix this?
P.S. Just in case, I clarify that I do not need line numbers in the source data. It is required only in the View for the convenience of the user.

Related

In WPF datatemplate, What I missed? data doesn't show correctly

I use a class 'SecondCondition' as basic data unit. It have 3 public property. ConditionColor, ConditionName, ConditionID
ObservableCollection 'SearchConditionList' is used as data list.
I made a datatemplate Binding like below.
< Model:SearchCondition x:Key="SearchCondition" />
< DataTemplate x:Key="ConditionSelector">
< StackPanel >
< xctk:ColorPicker x:Name="ConditionColorPicker"
SelectedColor="{Binding Path=ConditionColor,
Mode=TwoWay}">
< /xctk:ColorPicker>
< CheckBox x:Name="ConditionCheckbox"
Content="{Binding Path=ConditionName,
Mode=TwoWay}" />
< /StackPanel>
And I used the datatemplate at my Listbox.
< ListBox ItemsSource="{Binding Path=SearchConditionList}"
ItemTemplate="{StaticResource ConditionSelector}">
< /ListBox>
As result, I get number of blank template as much as List of items. But it doesn't show properties like color and name.
What I used as reference article use almost same and the code works, but mine is not. How can I solve this?
below is my reference.
https://learn.microsoft.com/ko-kr/dotnet/framework/wpf/data/data-templating-overview
Thank you.
P.S When I change codes like below, constant strings are shown very well but Bound value are not.
<StackPanel DataContext="{Binding Path=SearchConditionList}">
<ListBox ItemsSource="{Binding}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock Text="Why this doens't show bound value?"/>
<TextBlock Text=" : " />
<TextBlock Text="{Binding Path=ConditionName}"/>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
result is like below.
Here is my implementation based on your source code.
Model
public class SearchCondition
{
public Color ConditionColor { get; set; }
public string ConditionName { get; set; }
public string ConditionID { get; set; }
public SearchCondition(Color color, string name, string id)
{
ConditionColor = color;
ConditionName = name;
ConditionID = id;
}
}
ViewModel
public class ViewModel
{
public ObservableCollection<SearchCondition> SearchConditionList { get; set; }
public ViewModel()
{
SearchConditionList = new ObservableCollection<SearchCondition>();
SearchConditionList.Add(new SearchCondition(Colors.Red, "Red", "001"));
SearchConditionList.Add(new SearchCondition(Colors.Green, "Green", "002"));
}
}
The ViewModel is bound to the view in the code-behind.
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
Okay.. now this is the XAML.
<Window x:Class="DataBindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ConditionSelector">
<StackPanel Orientation="Horizontal">
<xctk:ColorPicker x:Name="ConditionColorPicker" SelectedColor="{Binding Path=ConditionColor, Mode=TwoWay}" />
<CheckBox x:Name="ConditionCheckbox" Content="{Binding Path=ConditionName, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Path=SearchConditionList}" ItemTemplate="{StaticResource ConditionSelector}" />
</Grid>
Screenshot
Honestly, I can't figure out where the problem is unless I see your full source code.
Visual Studio doesn't spit out exceptions explicitly. But you should be able to see the binding error in the output window. So you can find the clue.
Please compare your implementation with mine.

Wpf adding more views to tabcontrol dynamically with prism

I looking for a hint or just a direction how to design the structure I need because its a little tricky. I am using MVVM and Prism.
I have 2 regions, one with 2 buttons and one tabcontrol region. After clicking on button1 I want to inject 3 views as tabs into the tabcontrol. After clicking on button2 I want to remove the tabs from button1 (but keep the data behind) and inject again 3 views as tabs in the tabcontrol. And here comes the tricky part :-), this should happen dynamically and 2 of the 3 views from button1 and button2 are based on the same view/viewmodel.
I am thankfull for everything I can get :-D
You should be able to do this using the ViewModel first approach. Here is a quick and dirty way to do this, clean it up at your own convenience.
View/ViewModel structure:
View1 - ViewModel1 : IViewModel
View2 - ViewModel2 : IViewModel
View3 - ViewModel3 : IViewModel
IViewModel requires a "Content" string property
MainWindow.xaml
<Window x:Class="SFQ1.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:SFQ1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel1}">
<local:View1/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<local:View2/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel3}">
<local:View3/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<TabControl Grid.Row="0" ItemsSource="{Binding VmObservable}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
</ContentControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<Button Grid.Row="1" Margin="10,0,0,0" Width="100" Content="1"
HorizontalAlignment="Left" Command="{Binding SetOneCommand}"/>
<Button Grid.Row="1" Margin="130,0,0,0" Width="100" Content="2"
HorizontalAlignment="Left" Command="{Binding SetTwoCommand}"/>
</Grid>
</Window>
MainViewModel.cs
public class MainViewModel : BindableBase
{
private readonly List<IViewModel> _viewModelCollection;
private ObservableCollection<IViewModel> _vmObservable;
public ObservableCollection<IViewModel> VmObservable
{
get { return _vmObservable; }
set
{
SetProperty(ref _vmObservable, value);
RaisePropertyChanged();
}
}
private IViewModel _activeVm;
public IViewModel ActiveVm
{
get { return _activeVm; }
set
{
SetProperty(ref _activeVm, value);
RaisePropertyChanged();
}
}
public DelegateCommand SetOneCommand { get; set; }
public DelegateCommand SetTwoCommand { get; set; }
public MainViewModel()
{
SetOneCommand = new DelegateCommand(SetOne);
SetTwoCommand = new DelegateCommand(SetTwo);
VmObservable = new ObservableCollection<IViewModel>();
_viewModelCollection = new List<IViewModel>()
{
new ViewModel1(),
new ViewModel2(),
new ViewModel3()
};
}
private void SetOne()
{
VmObservable.Clear();
VmObservable.Add(GetViewModel(typeof(ViewModel1)));
VmObservable.Add(GetViewModel(typeof(ViewModel2)));
VmObservable.Add(GetViewModel(typeof(ViewModel3)));
}
private void SetTwo()
{
VmObservable.Clear();
VmObservable.Add(GetViewModel(typeof(ViewModel2)));
VmObservable.Add(GetViewModel(typeof(ViewModel3)));
}
private IViewModel GetViewModel( Type viewModelType )
{
return _viewModelCollection.Find(x => x.GetType().Equals(viewModelType));
}
}

Pulling different data into ListBox

How I should to do this? I tried the following:
In Xaml:
<DataTemplate x:Key="LogDataTemplate" DataType="data:Type1">
<TextBlock Text="Type1" />
</DataTemplate>
<DataTemplate x:Key="LogDataTemplate" DataType="data:Type2">
<TextBlock Text="Type2" />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<ListBox ItemsSource="{Binding source}"
ItemTemplate="{StaticResource LogDataTemplate}" />
</UserControl>
In view model(which is set as DataContext of the UserControl):
member x.source = new ObservableCollection<Object>()
But have an error about duplicates of DataTemplate
Remove the x:Key parameter. Implicit DataTemplates is what you want here.
Edit: Here is a really small working example :
MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.Windows;
namespace StackOverflow
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
Rectangles = new ObservableCollection<object>() { new RedRectangle(), new BlueRectangle() };
}
public ObservableCollection<object> Rectangles { get; set; }
}
public class RedRectangle { }
public class BlueRectangle { }
}
MainWindow.xaml
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow"
Width="500" Height="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:RedRectangle}">
<Rectangle Width="16" Height="16" Fill="Red" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:BlueRectangle}">
<Rectangle Width="16" Height="16" Fill="Blue" />
</DataTemplate>
</Window.Resources>
<ListBox ItemsSource="{Binding Rectangles}" />
</Window>
Well there's implicit data templates like #Sisyphe mentions.
But your real problem is, you've named both templates the same thing. x:Key is a dictionary key, it needs to be unique within its scope. That's what the error is about.
Having said that, you'll be better off with implicit data templates in this case as #Sisyphe mentions.

Silverlight PropertyGrid-like binding

I know that there are 3rd party silverlight property grids (in fact my company owns one) so please dont suggest 3rd party controls : I am trying to learn more about binding in xaml with this question.
I am writing a Silverlight front end as a sign facade to launch SSRS, and php based reports.
I have created a Report class with information about the report, and it has a parameters collection containing the information about parameters that need to be filled in to run the report.
My plan is to create a silverlight property grid that is bound to the Parameters collection of the Report.
Here's a simpler version of the classes:
public class Report
{
public int ReportId { get; set; }
public string ReportName { get; set; }
public string Description { get; set; }
private List<ReportParameter> _Parameters = new List<ReportParameter>();
public List<ReportParameter> Parameters
{
get { return _Parameters; }
set { _Parameters = value; }
}
}
public class ReportParameter
{
public int ReportId { get; set; }
public string ParameterName { get; set; }
public string DataTemplateName { get; set; }
public bool IsRequired { get; set; }
}
I was hoping to use the DataTemplateName property of ReportParameter to bind to data templates: for example if I have a parameter that is a date, I want to be able to set DataTemplateName="MyDatePicker" and then DataTemplate={StaticResource {Binding DataTemplateName}} and have that row use a DataTemplate defined in the Resources for editing the parameter value.
Here's some XAML I am using to try to get it to work:
<UserControl x:Class="ReportLauncherWorkbench.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ReportLauncherWorkbench"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<local:Report x:Key="MyData"
ReportName="Rick Report"
Description="Great report, try it!"
ReportId="0"
>
<local:Report.Parameters>
<local:XReportParameter DataTemplateName="DatePickerTemplate"
ParameterName="StartDate"
IsRequired="True"
Tooltip="Please enter the start date"
/>
<local:XReportParameter DataTemplateName="CheckBoxTemplate"
ParameterName="AmIHot"
IsRequired="True"
Tooltip="Please check here if you are hot"
/>
</local:Report.Parameters>
</local:Report>
<DataTemplate x:Key="DatePickerTemplate">
<StackPanel Orientation="Horizontal">
<TextBox />
<Button Content="..."/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="CheckBoxTemplate">
<StackPanel Orientation="Horizontal">
<CheckBox/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource MyData}">
<Grid x:Name="Test1" Background="White">
<StackPanel>
<TextBlock Text="{Binding ReportName}"/>
<TextBlock Text="{Binding Description}"/>
<ListBox ItemsSource="{Binding Parameters}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ParameterName}" Grid.Column="1" Margin="5"/>
<ListBox ItemsSource="{Binding}" Grid.Column="2" Width="100" ItemTemplate="{StaticResource {Binding DataTemplateName}}">
<!-- I want to somehow bind which DataTemplate is rendered-->
</ListBox>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</Grid>
</UserControl>
Thanks!
I figured it out - with the help of a great FAQ on the silverlight forum:
http://forums.silverlight.net/p/95440/218611.aspx
Silverlight does not have the DataTemplateSelector class that WPF does, which would have solved the problem.
In section titled
7.1 What data binding features of WPF are not yet supported in Silverlight? Is there a workaround?
There is a simple workaround that for DataTemplateSelector functionality.
So here's how I fixed it in my code sample:
Replace the Listbox with the following:
<ListBox ItemsSource="{Binding Parameters}">
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}" Loaded="ContentControl_Loaded"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And then fill in the ContentControl_Loaded event in the code behind with:
private void ContentControl_Loaded(object sender, RoutedEventArgs e)
{
ContentControl cc = (ContentControl) sender;
XReportParameter p = (XReportParameter)cc.DataContext;
cc.ContentTemplate = (DataTemplate)this.Resources[p.DataTemplateName];
}
Works great!

Interesting Issue with Silverlight Datagrid

Folks,
I'm having an interesting issue with Silverlight DataGrid data binding. It may be b/c I'm not binding the data source properly. Here's the object & the observable collection
/// <summary>
/// Interface for all model elements
/// </summary>
public interface IBaseModel
{
}
/// <summary>
/// Employee model
/// </summary>
public class EmployeeModel : IBaseModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString()
{
return FirstName + LastName;
}
}
// The observable collection is loaded and bound in the user control
public partial class EmployeeMasterDetailsWindow : UserControl
{
public EmployeeMasterDetailsWindow()
{
try
{
InitializeComponent();
ObservableCollection<IBaseModel> k = new ObservableCollection<IBaseModel>()
{new EmployeeModel(){FirstName="Frodo",
LastName=" Baggins"},
new EmployeeModel(){FirstName="Pippin",
LastName="Thomas"},
new EmployeeModel(){FirstName="John",
LastName="Doe"},
new EmployeeModel(){FirstName="Tim",
LastName="Kiriev"}};
dataGrid1.DataContext = k;
CustomersListBox.DataContext = k;
}
catch (Exception ex)
{
}
}
}
//here's the XAML
<UserControl x:Class="AdventureWorksManagement.UI.EmployeeMasterDetailsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="379" d:DesignWidth="516"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">
<UserControl.Resources>
<DataTemplate x:Key="CustomerTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" Height="371" Width="595">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="312*" />
<ColumnDefinition Width="283*" />
</Grid.ColumnDefinitions>
<sdk:DataGrid Height="325" HorizontalAlignment="Left"
Margin="12,12,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="271" ItemsSource="{Binding}"
RowDetailsTemplate="{StaticResource CustomerTemplate}">
</sdk:DataGrid>
<ListBox x:Name="CustomersListBox"
Margin="10,10,10,11"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource CustomerTemplate}" />
</Grid>
The Listbox shows all the of the employees, but the DataGrid doesn't. I don't even see the DataGrid. I see this error message in the output window:
'System.Collections.ObjectModel.ObservableCollection1[AdventureWorksManagement.Model.IBaseModel]'
'System.Collections.ObjectModel.ObservableCollection1[AdventureWorksManagement.Model.IBaseModel]'
(HashCode=54025633).
BindingExpression: Path='FirstName'
DataItem='System.Collections.ObjectModel.ObservableCollection`1[AdventureWorksManagement.Model.IBaseModel]'
(HashCode=54025633); target element is
'System.Windows.Controls.TextBlock'
(Name=''); target property is 'Text'
(type 'System.String')..
What could I be doing wrong?
By making it an ObservableCollection<IBaseModel> you are effectively casting all the child objects to IBaseModel, which has no members.
In this instance make it an ObservableCollection<EmployeeModel>.

Resources