Is there any way of feeding the columns of a Datagrid to a GridViewRowPresenter ?
It cannot be done directly, as one uses DataGridColumn and the other GridViewColumn so this doesn't work:
<GridViewRowPresenter Columns="{Binding ElementName=myDataGrid, Path=Columns, Mode=OneWay}" />
I haven't tried this, but something like this should work:
public class MyGridViewRowPresenter : GridViewRowPresenter
{
public static readonly DependencyProperty NumberOfColumnsProperty = DependencyProperty.Register("NumberOfColumns", typeof(int), typeof(MyGridViewRowPresenter), new PropertyMetadata(0));
public int NumberOfColumns
{
get { return (int)GetValue(NumberOfColumnsProperty); }
set { SetValue(NumberOfColumnsProperty, value); }
}
public override void EndInit()
{
base.EndInit();
for (var i = 0; i < NumberOfColumns; i++)
{
Columns.Add(new GridViewColumn());
}
}
}
usage
<local:MyGridViewRowPresenter NumberOfColumns="{Binding ElementName=myDataGrid, Path=Columns.Count, Mode=OneWay}" />
I did something similar to a standard grid, so instead of doing row or column definitions and then add column and rows, i would just say columns=somenumber and that would do it.
Related
I am building a DataGrid with DataGridTemplateColumns. The CellTemplate is created as DataTemplate in XAML:
<DataTemplate x:Key="StringCell">
<Grid DataContext="{Binding Path=Cells.Values[3]}">
<TextBlock Text="{Binding Path=AttributeValue.ObjectValue, Mode=OneWay}"
TextWrapping="Wrap"/>
</Grid>
</DataTemplate>
This is actually working, but I want to set the DataContext of the Grid in code when creating the Column. I tried this:
DataTemplate dt = cellTemplates["StringCell"] as DataTemplate;
(dt.LoadContent() as Grid).SetBinding(DataContextProperty, new Binding("Cells.Values[3]") { Mode = BindingMode.OneWay });
DataGridTemplateColumn dataGridTemplateColumn = new DataGridTemplateColumn()
{
CellTemplate = dt
};
but it's not working because LoadContent creates a new instance and doesn't change the template. Is there a way to set the DataContext in code?
Rather than modifying an existing template (which is not always possible), it's easier to create a new one from a string.
You didn't show the implementation of the classes used, so I'll show an example for such a class structure:
namespace Core2022.SO.ottoKranz
{
public class SomeClass
{
public SomeItem[]? Cells { get; set; }
}
public class SomeItem
{
public AttributeClass? AttributeValue { get; set; }
}
public class AttributeClass
{
public object? ObjectValue { get; set; }
}
}
And here is an example of creating a data template:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
namespace Core2022.SO.ottoKranz
{
public class CodeBehind
{
const string DataTemplateString = #"
<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:local='clr-namespace:Core2022.SO.ottoKranz'
DataType='local:SomeClass'>
<Grid DataContext='{{Binding Path=Cells.Values[{0}]}}'>
<TextBlock Text='{{Binding Path=AttributeValue.ObjectValue, Mode=OneWay}}'
TextWrapping='Wrap'/>
</Grid>
</DataTemplate>";
public static void OnGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
int index = 3;
string templateString =string.Format(DataTemplateString, index);
DataTemplate template = (DataTemplate) XamlReader.Parse(templateString);
DataGridTemplateColumn dataGridTemplateColumn = new DataGridTemplateColumn()
{
CellTemplate = template
};
e.Column = dataGridTemplateColumn;
}
}
}
I found a solution in this thread:
private DataTemplate GeneratePropertyBoundTemplate(int i, string templateKey)
{
DataTemplate cellTemplate = cellTemplates[templateKey] as DataTemplate; //Load DataTemplate from Resource
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(ContentPresenter));
factory.SetValue(ContentPresenter.ContentTemplateProperty, cellTemplate);
factory.SetBinding(ContentPresenter.ContentProperty, new Binding("Cells.Values[" + i + "]"));
return new DataTemplate { VisualTree = factory };
}
You just wrap the DataTemplate in a ContentPresenter and set his Binding.
As the columns of a data grid aren't in the visual tree of datagrid I'm m using this approach of Binding Proxy to bind Visibility of a DataGridTextColumn.
https://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
For some reason I would like to understand the same approach does work fine for Visibility but not for column's Widthproperty.
Could someone explain this different behavior to me?
Code sample
c#
class BindingProxy : Freezable
{
#region Override of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion //Override of Freezable
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
public class Column : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected internal void OnPropertyChanged(string propertyname)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
public Column(bool visible = true)
{
if (visible == true)
Visible = Visibility.Visible;
else
Visible = Visibility.Collapsed;
Width = 200;
}
public Visibility Visible
{
get { return m_visible; }
set { m_visible = value; OnPropertyChanged("Visible"); }
}
Visibility m_visible;
public double Width
{
get { return m_width; }
set { m_width = value; OnPropertyChanged("Width"); }
}
double m_width;
}
just to make it easier to reproduce
public partial class MainWindow : Window
{
public MainWindow()
{
Lines = new ObservableCollection<tableline>();
for (int i = 0; i< 5; i++)
Lines.Add(new tableline());
Columns = new List<Column>();
Columns.Add(new Column(true));
Columns.Add(new Column(false));
InitializeComponent();
DataContext = this;
}
public List<Column> Columns { get; set; }
public ObservableCollection<tableline>Lines { get; set; }
}
public class tableline
{
public tableline()
{
Result = new List<string>();
int colCount = 2;
for (int i = 0; i < colCount; i++)
{
Result.Add(i.ToString() + " some text");
}
}
public List<string> Result { get; set; }
}
xaml
<DataGrid ItemsSource="{Binding Lines}" AutoGenerateColumns="False" >
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="ProductId1" Binding="{Binding Path=Result[0]}" Visibility="{Binding Data.Columns[0].Visible, Source={StaticResource proxy}}" Width="{Binding Data.Columns[0].Width, Source={StaticResource proxy}}" />
<DataGridTextColumn Header="ProductId2" Binding="{Binding Path=Result[1]}" Visibility="{Binding Data.Columns[1].Visible, Source={StaticResource proxy}}" Width="{Binding Data.Columns[1].Width, Source={StaticResource proxy}}"/>
</DataGrid.Columns>
</DataGrid>
Just for you to know what my original goal is. I would like to set a width to auto for all columns, but with a "maximum" column width during drawing of datagrid. However, user should be possible to resize over this limit. That's why i can't use MaxColumnWidth So I thought easiest way to realize this would be to read width for every column and set it to "maximum" value if it's bigger than limit.
The type of the Width property of a DataGridColumn is DataGridLength and not double.
Change type of your source property:
public DataGridLength Width
{
get { return m_width; }
set { m_width = value; OnPropertyChanged("Width"); }
}
DataGridLength m_width;
And the Mode of the Binding:
<DataGridTextColumn Header="ProductId1" Binding="{Binding Path=Result[1]}"
Visibility="{Binding Data.Columns[0].Visible, Source={StaticResource proxy}}"
Width="{Binding Data.Columns[0].Width, Source={StaticResource proxy}, Mode=TwoWay}" />
EDITED: As comments have suggested, I should implement the MVVM pattern, I have done exactly that. However the same problem still persists. So I have altered the question accordingly:
I have a datagrid containing two columns bound to an observable collection (in the MyNotes class). One column contains a combobox and the other a textbox. The collection stores references to Note objects that contain an enumeration variable (displayed by the combobox) and a string (displayed by the textbox). All works fine except for the SelectedItems (and therefore the SelectedItem). When the program is built and run, you can add new rows to the datagrid (using the add/remove buttons) but after you attempt an edit (by entering the datagrid's textbox or combobox) then the datagrid's selectedItems and selectedItem fail. This can be seen by the use of the add/remove buttons: the selected row is not deleted and a new row is not added above the selected row respectively. This is a result of a symptom regarding the SelectedNote property losing its binding (I don't know why this happens and when I attempt to hack a rebind, the rebind fails?). Another symptom relates to the selected items property not reflecting what the datagrid is actually showing as selected (when viewing it in debug mode).
I am sure this problem relates to an issue with the datagrid, which makes it unusable for my case.
Here is the new XAML (its datacontext, the viewmodel, is set in the XAML and ParaTypes and headerText are both XAML static resources):
<DataGrid x:Name ="dgdNoteLimits"
ItemsSource ="{Binding ParagraphCollection}"
SelectedItem ="{Binding Path=SelectedNote, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AllowDrop ="True"
HeadersVisibility ="Column"
AutoGenerateColumns ="False"
CanUserAddRows ="False"
CanUserReorderColumns ="False"
CanUserSortColumns ="False"
BorderThickness ="0"
VerticalGridLinesBrush ="DarkGray"
HorizontalGridLinesBrush="DarkGray"
SelectionMode ="Extended"
SelectionUnit ="FullRow"
ColumnHeaderStyle ="{StaticResource headerText}">
<DataGrid.ItemContainerStyle>
<Style>
<Style.Resources>
<!-- SelectedItem's background color when focused -->
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Blue"/>
<!-- SelectedItem's background color when NOT focused -->
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}"
Color="Blue" />
</Style.Resources>
</Style>
</DataGrid.ItemContainerStyle>
<DataGrid.Columns>
<DataGridComboBoxColumn Header = "Note Type"
ItemsSource = "{Binding Source={StaticResource ParaTypes}}"
SelectedValueBinding= "{Binding Path=NoteType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextBinding = "{Binding Path=NoteType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
MinWidth = "115"
Width = "Auto">
</DataGridComboBoxColumn>
<DataGridTextColumn Header ="Description"
Binding="{Binding Path=NoteText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width ="*">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping"
Value ="Wrap"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="SpellCheck.IsEnabled"
Value ="true" />
<Setter Property="TextWrapping"
Value ="Wrap"/>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="1"
Margin="0, 0, 0, 16"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="Add"
Width="72"
Margin="16,8,8,8"
Command="{Binding AddClickCommand}"/>
<Button Content="Remove"
Width="72"
Margin="16,8,8,8"
Command="{Binding RemoveClickCommand}"/>
</StackPanel>
Here is the view model:
class MainWindowViewModel : INotifyPropertyChanged
{
MyNotes NotesCollection;
private bool canExecute;
private ICommand clickCommand;
public MainWindowViewModel()
{
this.NotesCollection = new MyNotes();
this.ParagraphCollection = this.NotesCollection.Notes;
this.canExecute = true;
}
private ObservableCollection<Note> paragraphCollection;
public ObservableCollection<Note> ParagraphCollection
{
get { return this.paragraphCollection; }
set
{
this.paragraphCollection = value;
RaisePropertyChanged(() => this.ParagraphCollection);
}
}
private Note selectedNote;
public Note SelectedNote
{
get { return this.selectedNote; }
set
{
if (this.selectedNote == value)
return;
this.selectedNote = value;
RaisePropertyChanged(() => this.SelectedNote);
}
}
public ICommand AddClickCommand
{
get
{
return this.clickCommand ?? (new ClickCommand(() => AddButtonHandler(), canExecute));
}
}
public void AddButtonHandler()
{
int noteIndex = 0;
Note aNote;
// what to do if a note is either selected or unselected...
if (this.SelectedNote != null)
{
// if a row is selected then add row above it.
if (this.SelectedNote.NoteIndex != null)
noteIndex = (int)this.SelectedNote.NoteIndex;
else
noteIndex = 0;
//create note and insert it into collection.
aNote = new Note(noteIndex);
ParagraphCollection.Insert(noteIndex, aNote);
// Note index gives sequential order of collection
// (this allows two row entries to have same NoteType
// and NoteText values but still note equate).
int counter = noteIndex;
// reset collection index so they are sequential
for (int i = noteIndex; i < this.NotesCollection.Notes.Count; i++)
{
this.NotesCollection.Notes[i].NoteIndex = counter++;
}
}
else
{
//if a row is not selected add it to the bottom.
aNote = new Note(this.NotesCollection.Count);
this.ParagraphCollection.Add(aNote);
}
}
public ICommand RemoveClickCommand
{
get
{
return this.clickCommand ?? (new ClickCommand(() => RemoveButtonHandler(), canExecute));
}
}
public void RemoveButtonHandler()
{
//delete selected note.
this.ParagraphCollection.Remove(selectedNote);
}
//boiler plate INotifyPropertyChanged implementation!
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged<T>(Expression<System.Func<T>> propertyExpression)
{
var memberExpr = propertyExpression.Body as MemberExpression;
if (memberExpr == null)
throw new ArgumentException("propertyExpression should represent access to a member");
string memberName = memberExpr.Member.Name;
RaisePropertyChanged(memberName);
}
protected virtual void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and the model:
class MyNotes
{
public ObservableCollection<Note> Notes;
public MyNotes()
{
this.Notes = new ObservableCollection<Note>();
}
}
public enum NoteTypes
{
Header, Limitation, Warning, Caution, Note
}
public class Note
{
public int? NoteIndex { get; set; }
public NoteTypes NoteType { get; set; }
public string NoteText { get; set; }
public Note()
{
this.NoteIndex = null;
this.NoteType = NoteTypes.Note;
this.NoteText = "";
}
public Note(int? noteIndex): this()
{
this.NoteIndex = noteIndex;
}
public override string ToString()
{
return this.NoteType + ": " + this.NoteText;
}
public override bool Equals(object obj)
{
Note other = obj as Note;
if (other == null)
return false;
if (this.NoteIndex != other.NoteIndex)
return false;
if (this.NoteType != other.NoteType)
return false;
if (this.NoteText != other.NoteText)
return false;
return true;
}
public override int GetHashCode()
{
int hash = 17;
hash = hash * 23 + this.NoteIndex.GetHashCode();
hash = hash * 23 + this.NoteType.GetHashCode();
hash = hash * 23 + this.NoteText.GetHashCode();
return hash;
}
}
Existing comments were greatly appreciated (I have learnt a lot and see the value of MVVM). It is just a shame they have not resolved the problem. But thank you.
So if anyone knows how I can resolve this issue, then that would be greatly appreciated.
I'm new to WPF and MVVM. I'm struggling to determine the best way to change the view of a chart. That is, initially a chart might have the axes: X - ID, Y - Length, and then after the user changes the view (either via lisbox, radiobutton, etc) the chart would display the information: X - Length, Y - ID, and after a third change by the user it might display new content: X - ID, Y - Quality.
My initial thought was that the best way to do this would be to change the bindings themselves. But I don't know how tell a control in XAML to bind using a Binding object in the ViewModel, or whether it's safe to change that binding in runtime?
Then I thought maybe I could just have a generic Model that has members X and Y and populate them as needed in the viewmodel?
My last thought was that I could have 3 different chart controls and just hide and show them as appropriate.
What is the CORRECT/SUGGESTED way to do this in the MVVM pattern? Any code examples would be greatly appreciated.
Thanks
Here's what I have for the bind to bindings method:
XAML:
<charting:Chart.Series>
<charting:BubbleSeries Name="bubbleSeries1"
ClipToBounds="False"
model:MakeDependencyProperty.IndependentValueBinding="{Binding AxisChoice.XBinding}"
model:MakeDependencyProperty.DependentValueBinding="{Binding AxisChoice.YBinding}"
model:MakeDependencyProperty.SizeValueBinding="{Binding AxisChoice.SizeBinding}"
IsSelectionEnabled="True" SelectionChanged="bubbleSeries1_SelectionChanged"
ItemsSource="{Binding Data}">
</charting:BubbleSeries>
</charting:Chart.Series>
<ComboBox Height="100" Name="listBox1" Width="120" SelectedItem="{Binding AxisChoice}">
<model:AxisGroup XBinding="{Binding Performance}" YBinding="{Binding TotalCount}" SizeBinding="{Binding TotalCount}" Selector.IsSelected="True"/>
<model:AxisGroup XBinding="{Binding ID}" YBinding="{Binding TotalCount}" SizeBinding="{Binding BadPerformance}"/>
<model:AxisGroup XBinding="{Binding ID}" YBinding="{Binding BadPerformance}" SizeBinding="{Binding TotalCount}"/>
</ComboBox>
AxisGroup:
public class AxisGroup : DependencyObject// : FrameworkElement
{
public Binding XBinding { get; set; }
public Binding YBinding { get; set; }
public Binding SizeBinding { get; set; }
}
DP:
public class MakeDependencyProperty : DependencyObject
{
public static Binding GetIndependentValueBinding(DependencyObject obj) { return (Binding)obj.GetValue(IndependentValueBindingProperty); }
public static void SetIndependentValueBinding(DependencyObject obj, Binding value) { obj.SetValue(IndependentValueBindingProperty, value); }
public static readonly DependencyProperty IndependentValueBindingProperty =
DependencyProperty.RegisterAttached("IndependentValueBinding", typeof(Binding), typeof(MakeDependencyProperty), new PropertyMetadata { PropertyChangedCallback = (obj, e) => { ((BubbleSeries)obj).IndependentValueBinding = (Binding)e.NewValue;}});
public static Binding GetDependentValueBinding(DependencyObject obj) { return (Binding)obj.GetValue(DependentValueBindingProperty); }
public static void SetDependentValueBinding(DependencyObject obj, Binding value) { obj.SetValue(DependentValueBindingProperty, value); }
public static readonly DependencyProperty DependentValueBindingProperty =
DependencyProperty.RegisterAttached("DependentValueBinding", typeof(Binding), typeof(MakeDependencyProperty), new PropertyMetadata { PropertyChangedCallback = (obj, e) => { ((BubbleSeries)obj).DependentValueBinding = (Binding)e.NewValue; } });
public static Binding GetSizeValueBinding(DependencyObject obj) { return (Binding)obj.GetValue(SizeValueBindingProperty); }
public static void SetSizeValueBinding(DependencyObject obj, Binding value) { obj.SetValue(SizeValueBindingProperty, value); }
public static readonly DependencyProperty SizeValueBindingProperty =
DependencyProperty.RegisterAttached("SizeValueBinding", typeof(Binding), typeof(MakeDependencyProperty), new PropertyMetadata { PropertyChangedCallback = (obj, e) => { ((BubbleSeries)obj).SizeValueBinding = (Binding)e.NewValue; } });
}
ViewModel:
public class BubbleViewModel : BindableObject
{
private IEnumerable<SessionPerformanceInfo> data;
public IEnumerable<SessionPerformanceInfo> Data { ... }
public AxisGroup AxisChoice;
}
This generates the following exception:
+ $exception {"Value cannot be null.\r\nParameter name: binding"} System.Exception {System.ArgumentNullException}
Has something to do with the 4 bindings in teh bubbleSeries. I'm more than likely doing something wrong with binding paths but as I said I'm new to binding and wpf, so any tips would be greatly appreciated.
Your initial thought was correct: You can bind to bindings, for example if you want to change both axes together you might have a ComboBox like this:
<ComboBox SelectedItem="{Binding AxisChoice}">
<my:AxisChoice XBinding="{Binding ID}" YBinding="{Binding Length}" />
<my:AxisChoice XBinding="{Binding Length}" YBinding="{Binding ID}" />
<my:AxisChoice XBinding="{Binding ID}" YBinding="{Binding Quality}" />
</ComboBox>
To make this work you need to declare XBinding and YBinding as CLR properties of type "Binding":
public class AxisChoice
{
public Binding XBinding { get; set; }
public Binding YBinding { get; set; }
}
Ideally you could then simply bind the DependentValueBinding or IndependentValueBinding of your chart:
<Chart ...>
<LineSeries
DependentValueBinding="{Binding AxisChoice.XBinding}"
IndependentValueBinding="{Binding AxisChoice.YBinding}" />
</Chart>
Unfortunately this does not work because DependentValueBinding and IndependentValueBinding aren't DependencyProperties.
The workaround is to create an attached DependencyProperty to mirror each property that is not a DependencyProperty, for example:
public class MakeDP : DependencyObject
{
public static Binding GetIndependentValueBinding(DependencyObject obj) { return (Binding)obj.GetValue(IndependentValueBindingProperty); }
public static void SetIndependentValueBinding(DependencyObject obj, Binding value) { obj.SetValue(IndependentValueBindingProperty, value); }
public static readonly DependencyProperty IndependentValueBindingProperty = DependencyProperty.RegisterAttached("IndependentValueBinding", typeof(Binding), typeof(MakeDP), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
((DataPointSeries)obj).IndependentValueBinding = (Binding)e.NewValue;
}
});
public static Binding GetDependentValueBinding(DependencyObject obj) { return (Binding)obj.GetValue(DependentValueBindingProperty); }
public static void SetDependentValueBinding(DependencyObject obj, Binding value) { obj.SetValue(DependentValueBindingProperty, value); }
public static readonly DependencyProperty DependentValueBindingProperty = DependencyProperty.RegisterAttached("DependentValueBinding", typeof(Binding), typeof(MakeDP), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
((DataPointSeries)obj).DependentValueBinding = (Binding)e.NewValue;
}
});
}
So your XAML becomes:
<Chart ...>
<LineSeries
my:MakeDP.DependentValueBinding="{Binding AxisChoice.XBinding}"
my:MakeDP.IndependentValueBinding="{Binding AxisChoice,YBinding}" />
</Chart>
If instead you want to change axes separately (two separate ComboBoxes or ListBoxes), you don't need AxisChoice: Simply make the Items or ItemsSource of each ComboBox consist of bindings, and put the a "XBinding" and "YBinding" properties directly in your view model.
Note that if your control exposes a regular property instead of a property of type Binding you can still use this method, but in this case you will use BindingOperations.SetBinding instead of just storing the binding value.
For example, if you want to change the binding of the text in a TextBlock from:
<TextBlock Text="{Binding FirstName}" />
to
<TextBlock Text="{Binding LastName}" />
based on a ComboBox or ListBox selection, you can use an attached property as follows:
<TextBlock my:BindingHelper.TextBinding="{Binding XBinding}" />
The attached property implementation is trivial:
public class BindingHelper : DependencyObject
{
public static BindingBase GetTextBinding(DependencyObject obj) { return (BindingBase)obj.GetValue(TextBindingProperty); }
public static void SetTextBinding(DependencyObject obj, BindingBase value) { obj.SetValue(TextBindingProperty, value); }
public static readonly DependencyProperty TextBindingProperty = DependencyProperty.RegisterAttached("TextBinding", typeof(BindingBase), typeof(BindingHelper), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
BindingOperations.SetBinding(obj, TextBlock.TextProperty, (BindingBase)e.NewValue)
});
}
I'm trying to simplify things so I made the ItemsSource of a ComboBox (Y1-Axis) consist of an observable collection of bindings, and I put the "YBinding" property directly in the ViewModel and set the public binding property as the combobox SelectedItem.
The the dependentvaluebinding is crashing the app though when using the public Binding SelectedY1:
<ComboBox Height="22" Name="comboBox1"
DisplayMemberPath="Source.MetricVarName"
ItemsSource="{Binding AllY1Choices}"
SelectedIndex="0"
SelectedItem="{Binding SelectedY1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</ComboBox>
<chartingToolkit:LineSeries
ItemsSource="{Binding AllY1Axis}"
IndependentValueBinding="{Binding AccumDate}"
my:MakeDP.DependentValueBinding="{Binding SelectedY1}">
In the VM:
private Binding _Y1axisChoice = new Binding();
private ObservableCollection<Binding> _allY1Choices = new ObservableCollection<Binding>();
public ObservableCollection<Binding> AllY1Choices
{
get { return _allY1Choices; }
set
{
_allY1Choices = value;
OnPropertyChanged("AllY1Choices");
}
}
private Binding _selectedY1 = new Binding();
public Binding SelectedY1
{
get { return _selectedY1; }
set
{
if (_selectedY1 != value)
{
_selectedY1 = value;
OnPropertyChanged("SelectedY1");
}
}
}
In the VM contstructor:
_Y1axisChoice = new Binding("MetricVarID");
_Y1axisChoice.Source = AllY1MetricVars[0];
_selectedY1 = _Y1axisChoice; // set default for combobox
_allY1Choices.Add(_Y1axisChoice);
_Y1axisChoice = new Binding("MetricVarID");
_Y1axisChoice.Source = AllY1MetricVars[1];
_allY1Choices.Add(_Y1axisChoice);
Any thoughts on this? The Binding object "SelectedY1" has Source.MetricID="OldA", and that's a valid value for the dependent value binding.
The error:
An exception of type 'System.InvalidOperationException' occurred in System.Windows.Controls.DataVisualization.Toolkit.dll but was not handled in user code
Additional information: Assigned dependent axis cannot be used. This may be due to an unset Orientation property for the axis or a type mismatch between the values being plotted and those supported by the axis.
Thanks
For a WPF project in need to save the width and the column order of a ListView because these are things the user can change. I guess it is no problem getting the current width but the current position seems to be a bit difficult.
In WinForms there was something like index and displayIndex but I don't see it in WPF.
How is it done?
BTW : Serializing the whole control is not an option.
Edit:
I found some samples using the listView.columns property. But I don't have such a property in my listView
My XAML code is like this:
<ListView>
<ListView.View>
<GridView>
<GridViewColumn>
....
I managed to do that using the Move(…) method of the GridView's Columns collection
If you have the new order stored somehow, you could try:
((GridView)myListView.View).Columns.Move(originalIndex, newIndex);
Edit: This is NOT XAML, but code you should put in the .xaml.cs file
The Columns are always in the same oder that the gridView.Columns Collection is. You can hook into the gridView.CollectionChanged event to react to changes, see also WPF Listview : Column reorder event?
I am using a Behavior to do this. There is a dependency property on the Behavior that is bound to my DataContext. You need a reference to System.Windows.Interactivityto use interactivity.
On my DataContext there is an ObservableCollection of ColumnInfo that I store in my configuration on application exit:
public class ColumnInfo
{
public string HeaderName { get; set; }
public int Width { get; set; }
public int Index { get; set; }
}
In your control add the namespace
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
And the ListView is something like
<ListView ItemsSource="{Binding SomeCollection}">
<ListView.View>
<GridView>
<i:Interaction.Behaviors>
<b:GridViewColumnBehavior Columns="{Binding Columns}" />
</i:Interaction.Behaviors>
</GridView>
</ListView.View>
</ListView>
The Behavior I'm using (parts of it)
public class GridViewColumnBehavior : Behavior<GridView>
{
public ObservableCollection<ColumnInfo> Columns
{
get { return (ObservableCollection<ColumnInfo>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(ObservableCollection<ColumnInfo>), typeof(GridViewColumnBehavior), new PropertyMetadata(null, new PropertyChangedCallback(Columns_Changed)));
private static void Columns_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var b = d as GridViewColumnBehavior;
if (b == null) return;
b.SetupColumns(e.NewValue as ObservableCollection<Column>);
}
public void SetupColumns(ObservableCollection<Column> oldColumns)
{
if(oldColumns != null)
{
oldColumns.CollectionChanged -= Columns_CollectionChanged;
}
if ((Columns?.Count ?? 0) == 0) return;
AssociatedObject.Columns.Clear();
foreach (var column in Columns.OrderBy(c => c.Index))
{
AddColumn(column);
}
Columns.CollectionChanged += Columns_CollectionChanged;
}
private void Columns_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
var lookup = AssociatedObject.Columns.Select((c, i) => new { Index = i, Element = c.Header.ToString() }).ToLookup(ci => ci.Element, ci => ci.Index);
foreach (var c in Columns)
{
// store the index in the Model (ColumnInfo)
c.Index = lookup[c.HeaderName].FirstOrDefault();
}
}
}
Enjoy!