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.
Related
I have a ComboBox bound to a collection of animals. From it I select my favourite animal. I need a static null item above the bound items. I declare it using a CompositeCollection. When the ComboBox is bound it does not select my initial favourite animal. How can I fix that? Similar problem here but still unresolved.
Observations:
Binding to the static item works i.e. if I don't have an initial favourite animal the static item gets selected.
The problem disappears if the static item is removed. Of course this would make the CompositeCollection and this whole question obsolete.
I already applied these measures:
A CollectionContainer cannot bind directly to a property as outlined here.
The composite collection is also moved to a static resource as suggested here.
Complete C# code and XAML to demonstrate the problem:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public class Animal
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Zoo
{
private IEnumerable<Animal> _animals = new Animal[]
{
new Animal() { Id = 1, Name = "Tom" },
new Animal() { Id = 2, Name = "Jerry" }
};
public Zoo(int initialId)
{
FavouriteId = initialId;
}
public int FavouriteId { get; set; }
public IEnumerable<Animal> Animals { get { return _animals; } }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void BindComboBox(object sender, RoutedEventArgs e)
{
// Selecting the static item by default works.
//DataContext = new Zoo(-1);
// Selecting "Jerry" by default does not work.
DataContext = new Zoo(2);
}
}
}
XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1">
<Window.Resources>
<CollectionViewSource x:Key="AnimalsBridge" Source="{Binding Path=Animals}" />
<CompositeCollection x:Key="AnimalsWithNullItem">
<local:Animal Id="-1" Name="Pick someone..."/>
<CollectionContainer Collection="{Binding Source={StaticResource AnimalsBridge}}" />
</CompositeCollection>
</Window.Resources>
<StackPanel>
<Button Content="Bind" Click="BindComboBox"/>
<ComboBox x:Name="cmbFavourite"
SelectedValue="{Binding Path=FavouriteId}"
SelectedValuePath="Id" DisplayMemberPath="Name"
ItemsSource="{StaticResource AnimalsWithNullItem}"/>
</StackPanel>
</Window>
I dont have a solution to your problem but rather an alternative. I personally have view models dedicated to each view. I would then have a property on the view model to add the null value as required. I prever this method since it allows for better unit testing of my viewmodel.
For your example add:
public class ZooViewModel
{
.....
public IEnumerable<Animal> Animals { get { return _animals; } }
public IEnumerable<Animal> AnimalsWithNull { get { return _animals.WithDefault(new Animal() { Id = -1, Name = "Please select one" }); } }
}
The magic component
public static class EnumerableExtend {
public static IEnumerable<T> WithDefault<T>(this IEnumerable<T> enumerable,T defaultValue) {
yield return defaultValue;
foreach (var value in enumerable) {
yield return value;
}
}
}
Then in your XAML you just bind to
ComboBox x:Name="cmbFavourite"
SelectedValue="{Binding Path=FavouriteId}"
SelectedValuePath="Id" DisplayMemberPath="Name"
ItemsSource="{Binding AnimalsWithNull }"/>
Now you are binding directly to the source and can control the binding as normal. Also note because we are using "yield" we are not creating a new enum but rather just iterating over the existing list.
I am using the WPF AutoCompleteBox and I have it working great, but one thing I would like to do is sort the suggestion list on the fly after each letter is entered into the primary TextBox. Does anyone know how to do this? I tried using an ICollectionView property with the DefaultView logic and adding SortDescriptions but it doesn't seem to phase the suggestion list. To make sure my collection view sorting was working I put a normal ListBox control and an AutoCompleteBox control on the same window and bound both controls to the same observable collection with the same collection view and the normal ListBox control showed the items sorted correctly using the SortDescriptions, but the AutoCompleteBox list didn't have the items sorted. It had them in the order they were added to the collection.
Thoughts? Suggestions? Has anyone done this?
I have no idea how #user1089031 done this, but here is working sample for anyone who could be interested in (updated to #adabyron's comment!):
ViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;
namespace WpfApplication12
{
public class Item
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
public class ViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate {};
private readonly ObservableCollection<Item> source;
private readonly ICollectionView items;
private string searchText;
public ViewModel()
{
source = new ObservableCollection<Item>
{
new Item {Name = "111111111 Test abb - (1)"},
new Item {Name = "22222 Test - (2)"},
new Item {Name = "333 Test - (3)"},
new Item {Name = "44444 Test abc - (4)"},
new Item {Name = "555555 Test cde - (5)"},
new Item {Name = "66 Test - bbcd (6)"},
new Item {Name = "7 Test - cd (7)"},
new Item {Name = "Test - ab (8)"},
};
items = new ListCollectionView(source);
}
public ICollectionView Items
{
get { return items; }
}
public IEnumerable<Item> ItemsSorted
{
get
{
return string.IsNullOrEmpty(SearchText)
? source
: (IEnumerable<Item>)source
.OrderBy(item => item.Name.IndexOf(SearchText,
StringComparison.InvariantCultureIgnoreCase));
}
}
public Item Selected { get; set; }
public string SearchText
{
get { return searchText; }
set
{
searchText = value;
PropertyChanged(this,
new PropertyChangedEventArgs("SearchText"));
PropertyChanged(this,
new PropertyChangedEventArgs("ItemsSorted"));
}
}
}
}
MainWindow.xaml:
<Window x:Class="WpfApplication12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit"
xmlns:wpfApplication2="clr-namespace:WpfApplication12"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Title="MainWindow" Height="200" Width="500"
DataContext="{DynamicResource viewModel}">
<Window.Resources>
<wpfApplication2:ViewModel x:Key="viewModel" />
<DataTemplate DataType="{x:Type wpfApplication2:Item}">
<TextBlock Text="{Binding Name}" FontFamily="Courier New" />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<controls:AutoCompleteBox
ItemsSource="{Binding ItemsSorted}"
FilterMode="ContainsOrdinal"
SelectedItem="{Binding Selected, Mode=TwoWay}"
MinimumPrefixLength="0"
VerticalAlignment="Top" Margin="5">
<i:Interaction.Behaviors>
<wpfApplication2:SearchTextBindBehavior
BoundSearchText="{Binding SearchText,
Mode=OneWayToSource}" />
</i:Interaction.Behaviors>
</controls:AutoCompleteBox>
<ListBox Grid.Column="1"
ItemsSource="{Binding Items}" Margin="5" />
</Grid>
</Window>
As you could notice I've add one custom behavior to AutoCompleteBox control:
<i:Interaction.Behaviors>
<wpfApplication2:SearchTextBindBehavior
BoundSearchText="{Binding SearchText,
Mode=OneWayToSource}" />
</i:Interaction.Behaviors>
This is because AutoCompleteBox's own SearchText property is read-only. So here is the code of this behavior:
SearchTextBindBehavior.cs (Updated)
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace WpfApplication12
{
public class SearchTextBindBehavior : Behavior<AutoCompleteBox>
{
public static readonly DependencyProperty BoundSearchTextProperty =
DependencyProperty.Register("BoundSearchText",
typeof(string), typeof(SearchTextBindBehavior));
public string BoundSearchText
{
get { return (string)GetValue(BoundSearchTextProperty); }
set { SetValue(BoundSearchTextProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.TextChanged += OnTextChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.TextChanged -= OnTextChanged;
}
private void OnTextChanged(object sender, RoutedEventArgs args)
{
if(AssociatedObject.Text.Length == 0)
{
BoundSearchText = string.Empty;
return;
}
if(AssociatedObject.SearchText ==
AssociatedObject.Text.Substring(0,
AssociatedObject.Text.Length - 1))
{
BoundSearchText = AssociatedObject.Text;
}
}
}
}
Note: To make it all work you will need to add reference to the System.Windows.Interactivity.dll from the Expression Blend 4 SDK. This is just where Behavior<T> and a few its friends live.
If you have Expression Blend already installed, you already have all the SDK and there is no need to download anything. Just in case - on my machine the assembly located here:
C:\Program Files\Microsoft SDKs\Expression\Blend.NETFramework\v4.0\Libraries\System.Windows.Interactivity.dll
And, finally, if you have some good reason to do NOT add reference to this popular official library, feel free to re-implemented this custom behavior in "the old way" via plain old attached properties.
Hope that helps.
This is what I ended up with, a slight adaptation of Sevenate's answer, so if you wanted to upvote, do that to his post.
I used a subclass (I had the AutoCompleteBox subclassed already for other reasons), which allows me to create a wrapper dependency property to get the readonly SearchText (=what the user entered via keyboard) to the ViewModel - instead of a blend behavior, which is a perfectly valid way, too.
The crux of the matter is that you should only apply the dynamic sorting upon changes of SearchText, not Text (=what is displayed in the AutoCompleteBox, will also change if a suggestion is selected in the dropdown). Sevenate's way to raise the PropertyChanged event of the readonly ItemsSource (ItemsSorted) is a nice and clean way to apply the sorting.
ViewModel:
public class Item
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
public class AutoCompleteBoxDynamicSortingVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly ObservableCollection<Item> source;
public AutoCompleteBoxDynamicSortingVM()
{
source = new ObservableCollection<Item>
{
new Item {Name = "111111111 Test abb - (1)"},
new Item {Name = "22222 Test - (2)"},
new Item {Name = "333 Test - (3)"},
new Item {Name = "44444 Test abc - (4)"},
new Item {Name = "555555 Test cde - (5)"},
new Item {Name = "66 Test - bbcd (6)"},
new Item {Name = "7 Test - cd (7)"},
new Item {Name = "Test - ab (8)"},
};
}
public IEnumerable<Item> ItemsSorted
{
get
{
return string.IsNullOrEmpty(Text) ? (IEnumerable<Item>)source :
source.OrderBy(item => item.Name.IndexOf(Text, StringComparison.OrdinalIgnoreCase));
}
}
public Item Selected { get; set; }
// Text that is shown in AutoCompleteBox
private string text;
public string Text
{
get { return text; }
set { text = value; OnPropertyChanged("Text"); }
}
// Text that was entered by user (cannot be changed from viewmodel)
private string searchText;
public string SearchText
{
get { return searchText; }
set
{
searchText = value;
OnPropertyChanged("SearchText");
OnPropertyChanged("ItemsSorted");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Subclass of AutoCompleteBox:
public class MyAutoCompleteBox : AutoCompleteBox
{
/// <summary>
/// Bindable property that encapsulates the readonly property SearchText.
/// When the viewmodel tries to set SearchText by way of EnteredText, it will fail without an exception.
/// </summary>
public string EnteredText
{
get { return (string)GetValue(EnteredTextProperty); }
set { SetValue(EnteredTextProperty, value); }
}
public static readonly DependencyProperty EnteredTextProperty = DependencyProperty.Register("EnteredText", typeof(string), typeof(MyAutoCompleteBox), new PropertyMetadata(null));
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
// synchronize SearchText and EnteredText (only one-way)
if (e.Property == AutoCompleteBox.SearchTextProperty && this.EnteredText != this.SearchText)
EnteredText = SearchText;
base.OnPropertyChanged(e);
}
}
Xaml:
<UserControl x:Class="WpfApplication1.Controls.AutoCompleteBoxDynamicSorting"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myctrls="clr-namespace:WpfApplication1.Controls"
xmlns:models="clr-namespace:WpfApplication1.ViewModels"
Height="350" Width="525"
DataContext="{DynamicResource viewModel}">
<UserControl.Resources>
<models:AutoCompleteBoxDynamicSortingVM x:Key="viewModel" />
<DataTemplate DataType="{x:Type models:Item}">
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</UserControl.Resources>
<Grid>
<myctrls:MyAutoCompleteBox
ItemsSource="{Binding ItemsSorted}"
Text="{Binding Text, Mode=TwoWay}"
EnteredText="{Binding SearchText, Mode=OneWayToSource}"
FilterMode="ContainsOrdinal"
VerticalAlignment="Top" Margin="5" />
</Grid>
</UserControl>
I am using WPF application
in this form have one gridview and one button
i am using the ObservableCollection have Generic and one class code like this
public partial class MainWindow : Window
{
public ObservableCollection<gm> data1 = new ObservableCollection<gm>();
public MainWindow()
{
InitializeComponent();
}
// public ObservableCollection<gm> data { get { return data1; } }
private void button1_Click_1(object sender, RoutedEventArgs e)
{
data1.Add(new gm() { no = 2, name = "vipul" });
dataGrid1.ItemsSource = data1.ToArray();
}
}
public class gm
{
public int no { get; set; }
public string name { get; set; }
}
}
when i am execute above code it add blank row in datagrid
please give me solution of this problem
i want to know how to add row in datagird run time.
thanks in advance
set autogeneratecolumns to true and just set data1 as itemssource
xaml
<DataGrid x:Name="dataGrid1" AutoGenerateColumns="True" />
EDIT: to get the power of WPF look into DataBinding/MVVM
XAML is like this:
<DataGrid ItemsSource="{Binding}" />
code behind is this:
public MainWindow()
{
InitializeComponent();
DataContext = data1;
}
and remove this line:
dataGrid1.ItemsSource = data1.ToArray();
data1.Add(new gm { no = 2, name = "vipul" }); // Remore ellipsis around gm.
In XAML just check the following that
You have DataSource of your datagrid to {Binding Path="data1"}
DataColumn of no have binding to {Binding Path="no"}
DataColumn of name have binding path to {Binding Path="name"}
I'm rather new to Silverlight and have a question about the notifying-mechanism. My solution is an MVVM-application stacked like this:
VIEW Contains a RadGridView bound to a collection in the viewmodel, the data is an entitycollection. The GridView's SelectedItem is bound to corresponding property in the viewmodel.
VIEWMODEL
Holds the properties below that the GridView is bound to and implements INotifyPropertyChanged.
•SelectList - an entitycollection that inherits ObservableCollection. When SelectList is set, it runs a notify-call.
•SelectedItem - an entity that also implements INotifyPropertyChanged for its own properties. When SelectedItem is set, it runs a notify-call.
My question is, who should make the notify-call so that the GridView knows value has changed? Occasionally, a property in the entity is set programmatically directly in the viewmodel. As for now, nothing is happening in the GUI although the properties gets the new values correctly.
Regards, Clas
-- UPDATE WITH CODE -------------------------
VIEW
<UserControl
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
x:Class="X.Y.Z.MonthReport.MonthReportView"
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:toolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot">
<telerik:RadGridView x:Name="MonthReportGrid"
Grid.Row="1"
ItemsSource="{Binding SelectList}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
AutoGenerateColumns="False">
<telerik:RadGridView.Columns>
<!-- The other columns have been cut out of this example -->
<telerik:GridViewDataColumn DataMemberBinding="{Binding curDate, Mode=TwoWay, TargetNullValue=''}" DataFormatString="{} {0:d}" Header="Avläst datum" UniqueName="curDate" IsVisible="True" IsReadOnly="False">
<telerik:GridViewDataColumn.CellEditTemplate>
<DataTemplate>
<telerik:RadDateTimePicker SelectedValue="{Binding curDate, Mode=TwoWay, TargetNullValue=''}" InputMode="DatePicker" DateTimeWatermarkContent="ÅÅÅÅ-MM-DD" />
</DataTemplate>
</telerik:GridViewDataColumn.CellEditTemplate>
</telerik:GridViewDataColumn>
<telerik:GridViewDataColumn DataMemberBinding="{Binding curValue, Mode=TwoWay, TargetNullValue=''}" Header="Avläst värde" UniqueName="curValue" IsVisible="True" IsReadOnly="False" />
</telerik:RadGridView>
</Grid>
</UserControl>
VIEW .CS
using System;
using System.Collections.Generic;
using System.Windows.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Windows.Controls;
using Telerik.Windows.Controls;
using Telerik.Windows.Controls.GridView;
namespace X.Y.Z.MonthReport
{
public partial class MonthReportView : UserControl, IMonthReportView
{
/// <summary>
/// ViewModel attached to the View
/// </summary>
public IMonthReportViewModel Model
{
get { return this.DataContext as IMonthReportViewModel; }
set { this.DataContext = value; }
}
public MonthReportView()
{
InitializeComponent();
this.MonthReportGrid.CellEditEnded += new EventHandler<GridViewCellEditEndedEventArgs>(MonthReportGrid_OnCellEditEnded);
}
public void MonthReportGrid_OnCellEditEnded(object sender, GridViewCellEditEndedEventArgs e)
{
if (e.Cell.Column.UniqueName == "curValue")
{
// ...
this.Model.SetAutomaticReadingDate();
}
if (e.Cell.Column.UniqueName == "curDate")
{
this.Model.UpdateAutomaticReadingDate();
}
}
}
}
VIEWMODEL
using System;
using Microsoft.Practices.Prism.Events;
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Prism.Commands;
namespace X.Y.Z.MonthReport
{
public class MonthReportViewModel : ViewModel<IMonthReportView>, IMonthReportViewModel
{
private readonly IEventAggregator eventAggregator;
private readonly IMonthReportService dataService;
private readonly IMonthReportController dataController;
private DateTime? _newReadingDate;
public DateTime? NewReadingDate
{
get { return _newReadingDate; }
set { _newReadingDate = value; }
}
//Holds the selected entity
private MonthReportEntity _selectedItem;
public MonthReportEntity SelectedItem
{
get { return _selectedItem; }
set
{
if (_selectedItem != value)
{
_selectedItem = value;
//The INotifyPropertyChanged implementation inherited from ViewModel-base.
Notify(() => this.SelectedItem);
}
}
}
//The entitycollection
private MonthReportEntityCollection _selectList;
public MonthReportEntityCollection SelectList
{
get { return _selectList; }
set
{
if (_selectList != value)
{
_selectList = value;
//The INotifyPropertyChanged implementation inherited from ViewModel-base.
Notify(() => this.SelectList);
}
}
}
public MonthReportViewModel(IMonthReportView view,
IEventAggregator eventAggregator, IMonthReportService dataService, IMonthReportController dataController)
{
this.InitializeCommands();
this.eventAggregator = eventAggregator;
this.dataController = dataController;
this.dataService = dataService;
this.View = view;
this.View.Model = this;
dataService.onGetMonthReportComplete += new EventHandler<MonthReportEventArgs>(OnGetMonthReportComplete);
dataService.onSaveMonthReportComplete += new EventHandler<MonthReportEventArgs>(OnSaveMonthReportComplete);
InitializeData();
}
public void InitializeCommands()
{
// ...
}
public void InitializeData()
{
GetMonthReport();
}
//This function is not working as I want it to.
//The gridview doesn't notice the new value.
//If a user edits the grid row, he should not need to
//add the date manually, Therefor I use this code snippet.
public void SetAutomaticReadingDate()
{
if ((NewReadingDate.HasValue) && (!SelectedItem.curDate.HasValue))
{
SelectedItem.curDate = NewReadingDate;
//The INotifyPropertyChanged implementation inherited from ViewModel-base.
Notify(() => this.SelectedItem.curDate);
}
}
public void GetMonthReport()
{
dataService.GetMonthReport();
}
public void SaveMonthReport()
{
dataService.SaveMonthReport(SelectList);
}
void OnGetMonthReportComplete(object sender, MonthReportEventArgs e)
{
// ...
}
void OnSaveMonthReportComplete(object sender, MonthReportEventArgs e)
{
// ...
}
#region ICleanable
public override void Clean()
{
base.Clean();
}
#endregion
}
}
if you do your binding like this
<telerik:GridViewDataColumn DataMemberBinding="{Binding curValue, Mode=TwoWay, TargetNullValue=''}" Header="Avläst värde" UniqueName="curValue" IsVisible="True" IsReadOnly="False" />
you just have to look at the binding to know where you have to call PropertyChanged and your binding said:
the class whith the property "curValue" has to implement INotifyProperyChanged to get the View informed.
public void SetAutomaticReadingDate()
{
if ((NewReadingDate.HasValue) && (!SelectedItem.curDate.HasValue))
{
//this is enough if the class of SelectedItem implements INotifyPropertyChanged
//and the curDate Poperty raise the event
SelectedItem.curDate = NewReadingDate;
}
}
btw BAD code style to name the Property curDate! it should be CurDate, Properties with camlCase hurts my eyes :)
Your "MonthReportEntityCollection" must implement interface "INotifyCollectionChanged" to allow informing UI about collection changes (items add/remove).
Your "MonthReportEntity" must implement interface "INotifyPropertyChanged" to allow informing UI about entitie's property changing.
Other stuff looks correct.
Tried may approches to displaying a "no data" if there are no items in listbox. Since I'm on wp7 and using silverlight I can't use DataTriggers, so I've created a control to have it behave consistently across the whole app. BUT I if you set the breakpoint for the set method - it's not being called at all!
The control class
public class EmptyListBox : ListBox
{
public new IEnumerable ItemsSource
{
get
{
return base.ItemsSource;
}
set
{
// never here
base.ItemsSource = value;
ItemsSourceChanged();
}
}
protected virtual void ItemsSourceChanged()
{
bool noItems = Items.Count == 0;
if (noItems)
{
if (Parent is System.Windows.Controls.Panel)
{
var p = Parent as Panel;
TextBlock noData = new TextBlock();
noData.Text = "No data";
noData.HorizontalAlignment = HorizontalAlignment;
noData.Width = Width;
noData.Height = Height;
noData.Margin = Margin;
p.Children.Add(noData);
Visibility = System.Windows.Visibility.Collapsed;
}
}
}
}
This is xaml
<my:EmptyListBox ItemsSource="{Binding Path=MyData}" Name="myListBox">
<my:EmptyListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=name}" />
</DataTemplate>
</my:EmptyListBox.ItemTemplate>
</my:EmptyListBox>
Codebehind:
ClientModel ClientInfo { get; set; }
public ClientView()
{
ClientInfo = new ClientModel();
ClientInfo.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(DataReady);
DataContext = ClientInfo
}
ClientModel class:
public class ClientModel : INotifyPropertyChanged
{
MyData _myData;
public MyData MyData
{
get
{
return _myData;
}
set
{
_myData = value;
NotifyPropertyChanged("MyData");
}
}
public void GetClient(int id)
{
// fetch the network for data
}
}
LINK TO SOLUTION .ZIP THAT SHOWS THE PROBLEM
http://rapidshare.com/files/455900509/WindowsPhoneDataBoundApplication1.zip
Your new ItemSource should be a DependencyProperty.
Anything that is working with Bindings have to be a DependencyProperty.
Simply make it a DependencyProperty.
I think the solution I'd go for is something like this:
Define a new visual state group ItemsStates and two visual states: NoItems and HasItems.
In the ControlTemplate for your custom listbox, add the visual tree for your "no data" state.
In the NoItems state, set the Visibility of your "no data" elements to Visible and set the Visibility of the default ItemsPresenter to Collapsed.
In the HasItems state, swap the Visibility of these elements.
In an OnApplyTemplate override switch to the Empty state by default: VisualStateManager.GoToState(this, "Empty", true);
In an OnItemsChanged override, check whether the items source is empty and use VisualStateManager to switch between these states accordingly.
That should work :)
Create ItemsSource as a DependencyProperty.
Example:
public IEnumerable ItemsSource
{
get { return (IEnumerable)base.GetValue(ItemsSourceProperty); }
set { base.SetValue(ItemsSourceProperty, value); }
}
public static DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
"ItemsSource",
typeof(IEnumerable),
typeof(EmptyListBox),
new PropertyMetadata(null));
try to implement the INotifyPropertyChanged interface and use for ItemsSource an ObservableCollection. In the Setter of your Property just call the OnPropertyChanged method.
Maybe this will help.
Try adding Mode=TwoWay to the ItemsSource binding:
<my:EmptyListBox ItemsSource="{Binding Path=MyData, Mode=TwoWay}" Name="myListBox">
<my:EmptyListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=name}" />
</DataTemplate>
</my:EmptyListBox.ItemTemplate>
</my:EmptyListBox>