Silverlight 4: Pattern for displaying data with a bit of logic? - silverlight

I'm building a wp7 app.
I have a UserControl that displays a news article headline, teaser, and image. The entire class is pretty short:
public partial class StoryControl : UserControl
{
public Story Story { get; private set; }
public StoryControl()
{
InitializeComponent();
}
internal StoryControl(Story story) : this()
{
this.Story = story;
Teaser.Text = story.Teaser;
Headline.Text = story.Title;
if (story.ImageSrc == null)
{
Thumbnail.Visibility = Visibility.Collapsed;
} else
{
Thumbnail.Source = new BitmapImage(story.ImageSrc);
}
}
}
And the corresponding XAML:
<Grid x:Name="LayoutRoot" Background="Transparent" Margin="0,0,0,20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image x:Name="Thumbnail" Grid.Column="0" Width="89" HorizontalAlignment="Left" VerticalAlignment="Top" />
<!-- sometimes there's a hanging word in the headline that looks a bit awkward -->
<TextBlock x:Name="Headline" Grid.Column="1" Grid.Row="0" Style="{StaticResource PhoneTextAccentStyle}" TextWrapping="Wrap" HorizontalAlignment="Left" FontSize="23.333" VerticalAlignment="Top" />
<TextBlock x:Name="Teaser" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" HorizontalAlignment="Left" Style="{StaticResource PhoneTextSubtleStyle}" TextWrapping="Wrap" VerticalAlignment="Top" Width="384"/>
</Grid>
Is there a way to do with with less code-behind and more XAML? Some way to use binding to bind the text for Headline and Teaser to properties of the Story, while not crashing if Story is null?
About about the image? I've got a bit of logic there; is there some way to automatically do that in XAML, or am I stuck with that in C#?

Looks like a ViewModel is in order:
public class StoryViewModel
{
readonly Story story;
public StoryViewModel(Story story)
{
this.story = story;
}
public string Teaser { get { return story == null ? "" : story.Teaser; } }
public string Title { get { return story == null ? "" : story.Title; } }
public bool IsThumbnailVisible { get { return story != null && story.ImageSrc != null; } }
public BitmapImage Thumbnail { get { return IsThumbnailVisible ? new BitmapImage(story.ImageSrc) : null; } }
}
Making your codebehind nice and simple:
public partial class StoryControl : UserControl
{
public Story Story { get; private set; }
public StoryControl()
{
InitializeComponent();
}
internal StoryControl(Story story)
: this()
{
this.DataContext = new StoryViewModel(story);
}
}
And your XAML becomes a set of bindings:
<Grid x:Name="LayoutRoot" Background="Transparent" Margin="0,0,0,20">
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="booleanToVisiblityConverter"/>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Visibility="{Binding IsThumbnailVisible, Converter={StaticResource booleanToVisiblityConverter}}" Source="{Binding Thumbnail}" Grid.Column="0" Width="89" HorizontalAlignment="Left" VerticalAlignment="Top" />
<!-- sometimes there's a hanging word in the headline that looks a bit awkward -->
<TextBlock Text="{Binding Title}" Grid.Column="1" Grid.Row="0" Style="{StaticResource PhoneTextAccentStyle}" TextWrapping="Wrap" HorizontalAlignment="Left" FontSize="23.333" VerticalAlignment="Top" />
<TextBlock Text="{Binding Teaser}" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" HorizontalAlignment="Left" Style="{StaticResource PhoneTextSubtleStyle}" TextWrapping="Wrap" VerticalAlignment="Top" Width="384"/>
</Grid>
Ok, it would probably be possible to do this with just model (story) and view (xaml) via fallbackvalues and more complex converters, but i hope you'll find that viewmodels give you the most power in terms of testable, brick-wall-less, view-specific logic...

Related

How to bind XamDataGrids available on different panels with different collections on a WPF page, using pure MVVM concepts & dependency injection.

I have a WPF XAML page, having 3 sections separated by DockPanels. One panel contains an INFRAGITICS XamDataGrid control to be bound with a collection.
I would like to bind XamDataGrid control using DataContext/DataSource property in pure MVVM way.
Also, would be delighted to understand if binding is done through dependency injection.
I have tried different approaches but didn't get success. I have pasted my code below for reference. Kindly help.
XAML Page:
<Window x:Class="UserInterface.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:UserInterface"
xmlns:igDP="clr-namespace:Infragistics.Windows.DataPresenter;assembly=InfragisticsWPF.DataPresenter"
xmlns:igEditors="clr-namespace:Infragistics.Windows.Editors;assembly=InfragisticsWPF.Editors"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:dc ="clr-namespace:UserInterface.ViewModel"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
mc:Ignorable="d"
Title="MainWindow">
<Window.Resources>
<dc:GraphicViewModel x:Key="dataContext"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=".5*"/>
<RowDefinition Height=".5*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".5*"/>
<ColumnDefinition Width=".5*"/>
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="0" Grid.Row="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
</Grid>
<!--<StackPanel Orientation="Vertical" DockPanel.Dock="Top">
<StackPanel Orientation="Horizontal" Margin="5" Grid.Row="0">
<DockPanel>
<TextBlock Text="*.cfg File" Grid.Column="0" DockPanel.Dock="Left"/>
<Button Content="Browse..." Grid.Column="2" DockPanel.Dock="Right"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse *.cfg file..." Grid.Column="1" DockPanel.Dock="Right"/>
</DockPanel>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5" Grid.Row="1">
<TextBlock Text="*.ps File " Grid.Column="0"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse *.ps file..." Grid.Column="1"/>
<Button Content="Browse..." Grid.Column="2"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5" Grid.Row="2">
<TextBlock Text="*.pic File " Grid.Column="0"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse *.pic file..." Grid.Column="1"/>
<Button Content="Browse..." Grid.Column="2"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5" Grid.Row="3">
<TextBlock Text="*.xlsx File" Grid.Column="0"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse *.xlsx file..." Grid.Column="1"/>
<Button Content="Browse..." Grid.Column="2"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5" Grid.Row="4">
<TextBlock Text="*.xlsx File"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse *.xlsx file..." Grid.Column="1"/>
<Button Content="Browse..." Grid.Column="2"/>
</StackPanel>
</StackPanel>-->
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical" Margin="5" Grid.Row="0">
<TextBlock MinHeight="20.5" Text="*.cfg File" Grid.Column="0"/>
<TextBlock MinHeight="20.5" Text="*.ps File " Grid.Column="0"/>
<TextBlock MinHeight="20.5" Text="*.pic File " Grid.Column="0"/>
<TextBlock MinHeight="20.5" Text="*.xlsx File" Grid.Column="0"/>
<TextBlock MinHeight="20.5" Text="*.xlsx File" Grid.Column="0"/>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="5" Grid.Row="1">
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse *.cfg file..." Grid.Column="1" MinHeight="20.5"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse *.ps file..." Grid.Column="1" MinHeight="20.5"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse *.pic file..." Grid.Column="1" MinHeight="20.5"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse Model mapping file..." Grid.Column="1" MinHeight="20.5"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse Parameter mapping file..." Grid.Column="1" MinHeight="20.5"/>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="5" Grid.Row="2">
<Button Content="Browse..." Grid.Column="2"/>
<Button Content="Browse..." Grid.Column="2"/>
<Button Content="Browse..." Grid.Column="2"/>
<Button Content="Browse..." Grid.Column="2"/>
<Button Content="Browse..." Grid.Column="2"/>
</StackPanel>
</StackPanel>
</DockPanel>
<DockPanel Grid.Column="1" Grid.Row="0">
<igDP:XamDataGrid x:Name="ItemsSource" DataContext="{Binding Source={StaticResource dataContext}, Path=ItemsSource, Mode=TwoWay}" Grid.Row="0" Margin="10" AutoFit="true">
<igDP:XamDataGrid.ViewSettings>
<igDP:GridViewSettings/>
</igDP:XamDataGrid.ViewSettings>
<igDP:XamDataGrid.FieldSettings>
<igDP:FieldSettings LabelTextAlignment="Left" AllowRecordFiltering="true" FilterOperandUIType="ExcelStyle" FilterStringComparisonType="CaseInsensitive" FilterOperatorDefaultValue="Contains"
LabelClickAction="SortByOneFieldOnlyTriState" SortComparisonType="Default"/>
</igDP:XamDataGrid.FieldSettings>
<igDP:XamDataGrid.FieldLayoutSettings>
<igDP:FieldLayoutSettings DataErrorDisplayMode="ErrorIconAndHighlight" SupportDataErrorInfo="RecordsAndCells" SelectionTypeRecord ="Single"
AutoGenerateFields="False" FilterUIType="FilterRecord"/>
</igDP:XamDataGrid.FieldLayoutSettings>
<igDP:XamDataGrid.FieldLayouts>
<igDP:FieldLayout>
<igDP:FieldLayout.Fields>
<igDP:Field Name="IsSelected" Label="Select" HorizontalContentAlignment="Left" Width="Auto" VerticalContentAlignment="Center">
<igDP:Field.Settings>
<igDP:FieldSettings DataItemUpdateTrigger="OnCellValueChange">
<igDP:FieldSettings.LabelPresenterStyle>
<Style TargetType="{x:Type igDP:LabelPresenter}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked" Content="" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</igDP:FieldSettings.LabelPresenterStyle>
</igDP:FieldSettings>
</igDP:Field.Settings>
</igDP:Field>
<igDP:Field Label="Name" Name="Name" AllowEdit="False" HorizontalContentAlignment="Left" Width="Auto">
</igDP:Field>
<igDP:Field Label="Type" Name="Type" AllowEdit="False" HorizontalContentAlignment="Left" Width="*"/>
<igDP:Field Label="Background" Name="Background" AllowEdit="False" HorizontalContentAlignment="Left" Width="Auto"/>
<igDP:Field Label="Width" Name="Width" AllowEdit="False" HorizontalContentAlignment="Right" Width="Auto"/>
<igDP:Field Label="Height" Name="Height" AllowEdit="False" HorizontalContentAlignment="Right" Width="Auto"/>
</igDP:FieldLayout.Fields>
</igDP:FieldLayout>
</igDP:XamDataGrid.FieldLayouts>
</igDP:XamDataGrid>
</DockPanel>
<DockPanel Grid.Column="0" Grid.Row="1" Grid.RowSpan="2">
<StackPanel Orientation="Vertical" DockPanel.Dock="Bottom">
<TextBox Text="Sample Text1"/>
<TextBox Text="Sample Text2"/>
<TextBox Text="Sample Text3"/>
<TextBox Text="Sample Text4"/>
</StackPanel>
</DockPanel>
<!--</StackPanel>-->
</Grid>
</Window>
Xaml page code behind:
namespace UserInterface
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//GraphicViewModel obj = new GraphicViewModel();
//ItemsSource.DataSource = obj.ItemsSource;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion INotifyPropertyChanged Members
public GraphicViewModel GraphicViewModel
{
get { return this.DataContext as GraphicViewModel; }
set
{
this.DataContext = value;
if (this.DataContext != null)
NotifyPropertyChanged("GraphicViewModel");
}
}
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
}
}
}
Model Class:
namespace UserInterface.Model
{
public class GraphicsModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyOfPropertyChange(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected == value) return;
_isSelected = value;
NotifyOfPropertyChange("IsSelected");
}
}
private string _name = string.Empty;
public string Name
{
get { return _name; }
set
{
if (_name != value)
_name = value;
NotifyOfPropertyChange("Name");
}
}
private string _type = string.Empty;
public string Type
{
get { return _type; }
set
{
if (_type != value)
_type = value;
NotifyOfPropertyChange("Type");
}
}
private string _width = string.Empty;
public string Width
{
get { return _width; }
set
{
if (_width != value)
_width = value;
NotifyOfPropertyChange("Width");
}
}
private string _height = string.Empty;
public string Height
{
get { return _height; }
set
{
if (_height != value)
_height = value;
NotifyOfPropertyChange("Height");
}
}
}
}
ViewModel class:
namespace UserInterface.ViewModel
{
public class GraphicViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyOfPropertyChange(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
private ObservableCollection<GraphicsModel> _itemsSource = new ObservableCollection<GraphicsModel>();
// MainWindow _view = null;
public ObservableCollection<GraphicsModel> ItemsSource
{
get { return _itemsSource; }
set
{
if (_itemsSource == value) return;
_itemsSource = value;
NotifyOfPropertyChange("ItemsSource");
}
}
public GraphicViewModel()
{
//_view = view;
_itemsSource = new ObservableCollection<GraphicsModel>() { new GraphicsModel() { Name = "sdasdad", Type = "Model", IsSelected = false, Height = "1000", Width = "1370" } ,
new GraphicsModel() { Name = "sdsa", Type = "Model", IsSelected = false, Height = "1000", Width = "1370" } ,new GraphicsModel() { Name = "sdasdad", Type = "Model", IsSelected = false, Height = "1000", Width = "1370" } ,new GraphicsModel() { Name = "asas", Type = "Model", IsSelected = false, Height = "1000", Width = "1370" } ,new GraphicsModel() { Name = "rewwe", Type = "Model", IsSelected = false, Height = "1000", Width = "1370" } ,};
//view.GraphicViewModel = this;
}
}
}
When you write
<Window.Resources>
<dc:GraphicViewModel x:Key="dataContext"/>
</Window.Resources>
You are saying "create a property of type GraphicViewModel and name it dataContext". It's almost the exact same as doing something like this in the code-behind :
private GraphicViewModel dataContext = new GraphicViewModel();
When you write
<igDP:XamDataGrid DataContext="{Binding Source={StaticResource dataContext}, Path=ItemsSource, Mode=TwoWay}" .. />
You are saying "bind the igDP:XamDataGrid.DataContext property to dataContext.ItemsSource".
Now for why the Grid won't load, the .DataContext property doesn't actually do anything. It's just a holder for the data that sits behind the control. So if you were to write
<igDP:XamDataGrid ItemsSource="{Binding MyProperty}" />
It would be to tell WPF "set XamDataGrid.ItemsSource property equal to XamDataGrid.DataContext.MyProperty".
Since in the code above you set the DataContext equal to dataContext.ItemsSource, it would try to set the value to dataContext.ItemsSource.MyProperty (which of course does not exist).
What you actually need is something like
<igDP:XamDataGrid ItemsSource="{Binding Source={StaticResource dataContext}, Path=ItemsSource}" .. />
This binds the .ItemsSource property of the grid (which is the property it builds its data rows from) to the static resource created in <Window.Resources> named "dataContext", and the property on that object called ItemsSource.
Second issue here though is you seem to have multiple copies of your ViewModel in your code. Personally I recommend never creating objects in the XAML like you did for your GraphicViewModel. Instead, I would recommend something like this :
public MainWindow()
{
InitializeComponent();
this.DataContext = new GraphicViewModel();
}
Now it should be noted that this creates a new instance of the GraphicViewModel, but doesn't store it anywhere. If you want to access this object in other code-behind without casting this.DataContext to GraphicViewModel, then you should probably store the value somewhere.
Once the Window.DataContext is set, you can write the bindings without specifying a custom Source property for the binding.
<igDP:XamDataGrid ItemsSource="{Binding ItemsSource}" .. />
This will work because WPF controls will look up the DataContext from their parent by default if it is not specifically set. So in this case, the .DataContext property for XamDataGrid is null so it will travel up the visual tree looking for something with a DataContext until it runs into Window.DataContext which you've set in the constructor to a new instance of your GraphicViewModel.
For people looking to understand the DataContext property, I usually send them to this SO answer to avoid having to retype the same thing all the time. I'd recommend reading it if you're still working to understand what the DataContext is or how it's used.
Also that said, Dependency Injection is something different and wouldn't be used here at all.
Thanks a lot #Rachel, #Daniel Filipov & #quetzalcoatl, I got my code working now. The changes done in below files:
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
ItemsSource.DataContext= new GraphicViewModel();
}
MainWindow.xaml:
<igDP:XamDataGrid x:Name="ItemsSource" DataSource="{Binding ItemsSource, Mode=TwoWay}" .../>

WPF Databinding not working in both directions when DataGrid bound to Observable Collection of Properties with Sub properties

Excuse the wordy title, I'm having trouble with a succinct description. If I could come up with one, I could probably Google the right answer!
I am binding my DataGrid to an ObservableCollection of properties that themselves have properties. My grid is populated just fine, but when I edit the grid the changes are not getting back to my model.
I have an ObservableCollection
Normally, you'd just have some properties of MarriedCoupleRow, but I actually have something slightly more complicated. Each MarriedCoupleRow has some propties (Male, Female) which in turn expose properties (Height, Weight, Information). It's this Information that can be edited. Again, I can populate the grid just fine, but the setter property of Information is not hit when you edit the cell and tab off (or leave).
I'd appreciate any pointers or references, including how to better word my title!
Here's the simple code:
public class XMLDemoViewModel : ViewModelBase
{
public XMLDemoViewModel()
{
_rows = new ObservableCollection<MarriedCoupleRow>();
// create some data....
for (uint i = 0; i < 2;i++)
{
MarriedCoupleRow row = new MarriedCoupleRow();
row.Male = new HumanData();
row.Male.Height = (70 + i*5).ToString();
row.Male.Weight = 150+(i*30+1);
row.Male.Information = row.Male.Height + " " + row.Male.Weight;
row.Female = new HumanData();
row.Female.Height = (60 +i*3).ToString();
row.Female.Weight = 120+(i*10+5);
row.Female.Information = row.Female.Height + " " + row.Female.Weight;
_rows.Add(row);
}
}
#region Fields
private ObservableCollection<MarriedCoupleRow> _rows = null;
#endregion Fields
#region Properties
public ObservableCollection<MarriedCoupleRow> Rows
{
get
{
return _rows;
}
}
#endregion Properties
#region Commands
#endregion Commands
#region Private Methods
#endregion Private Methods
}
public class MarriedCoupleRow : ViewModelBase
{
private HumanData _Male = null;
public HumanData Male
{
get { return _Male; }
set
{
if (value != _Male)
{
_Male = value;
OnPropertyChanged("Male");
}
}
}
public HumanData Female { get; set; }
}
public class HumanData : INotifyPropertyChanged
{
public string Height { get; set; }
public uint Weight { get; set; }
private string _friendlyName;
public string Information
{
get
{
return _friendlyName;
}
set
{
if (_friendlyName != value)
{
_friendlyName = value;
OnPropertyChanged("Information");
}
}
}
}
And here's the XAML:
<Window x:Class="XMLDemo.Views.XMLDemoView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="XMLDemoView" Height="600" Width="1152">
<DockPanel>
<StackPanel Orientation="Horizontal" Height="120" DockPanel.Dock="Bottom">
<StackPanel Orientation="Horizontal">
<GroupBox Width="249" BorderThickness="2" Height="90">
<GroupBox.Header>
<TextBlock FontSize="12" FontWeight="Bold">Control</TextBlock>
</GroupBox.Header>
<Grid Height="64" Width="223">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="0.5*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.5*"/>
<RowDefinition Height="0.5*"/>
</Grid.RowDefinitions>
</Grid>
</GroupBox>
</StackPanel>
</StackPanel>
<DataGrid ItemsSource="{Binding Path=Rows, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate >
<DataTemplate>
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="Male" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Male.Height}" IsEnabled="False" Grid.Row="0"></TextBox>
<TextBox Text="{Binding Male.Weight}" IsEnabled="False" Grid.Row="1"></TextBox>
<TextBox Text="{Binding Male.Information, Mode=TwoWay}" Grid.Row="2"></TextBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate >
<DataTemplate>
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="Female" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid >
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Female.Height}" IsEnabled="False" Grid.Row="0"></TextBox>
<TextBox Text="{Binding Female.Weight}" IsEnabled="False" Grid.Row="1"></TextBox>
<TextBox Text="{Binding Female.Information}" Grid.Row="2"></TextBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
Your TextBox.Text bindings need to be set to updatesource on propertychanged.
<TextBox Text="{Binding Male.Information, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Grid.Row="2"></TextBox>
There must be something goofy going on with LostFocus which is the default. I tested this using your code.
I Like your post. As I seen from the coding you are using Complex property binding with DataGrid. Also complex property changes won't be reflect in DataGrid.
However you can achieve this requirement in sample level. I will try to make the sample and let you know.
Regards,
Riyaj Ahamed I

WPF TreeView DataBinding: data not shown, BindingExpression path error

I'm working on a new functionality for Visual Studio Add-in. Initially the project's target framework was 3.5. But I was asked to add a Tool Window with quite complicated UI using WPF and switch to 4.0 framework (maybe this is important)
I'm trying to bind hierarchical data to the Tree View inside my Tool Window which is originally a WPF User Control.
But I see the following error:
"System.Windows.Data Error: 40 : BindingExpression path error: 'PoolList' property not found on 'object' ''OpjectPool' (Name='')'. BindingExpression:Path=PoolList; DataItem='OpjectPool' (Name=''); target element is 'TreeView' (Name='treeView1'); target property is 'ItemsSource' (type 'IEnumerable')"
This is a Class which represents data I need to bind to the tree view.
class CodeItem
{
public TextPoint StartPoint { get; set; }
public TextPoint EndPoint { get; set; }
public string Name { get; set; }
public vsCMElement Kind { get; set; }
public CodeElements ChildClasses { get; set; }
public ProjectItem ProjectItem { get; set; }
public List<CodeItem> CodeItems { get; set; }
public string Label { get; set; }
public CodeItem(CodeElement el)
{
StartPoint = el.StartPoint;
EndPoint = el.EndPoint;
Name = el.Name;
Kind = el.Kind;
ChildClasses = el.Children;
ProjectItem = el.ProjectItem;
Label = Kind.ToString();
CodeItems = new List<CodeItem>();
if (ChildClasses.Count != 0)
{
foreach (CodeElement elem in ChildClasses)
{
if (elem.Kind.ToString() == "vsCMElementClass")
{
CodeItems.Add(new CodeItem(elem));
}
}
}
}
}
Here is my UserControl code:
public partial class OpjectPool : UserControl
{
public ObservableCollection<CodeItem> PoolList = new ObservableCollection<CodeItem>();
public OpjectPool()
{
Project pr = ... // getting VS Project we want to work with;
foreach (ProjectItem item in pr.ProjectItems.Item("Objects").ProjectItems)
{
if (item.FileCodeModel != null)
{
CodeItem rootPoolItem = new CodeItem(item.Name);
foreach (CodeElement el in item.FileCodeModel.CodeElements)
{
if (el.Kind.ToString() == "vsCMElementClass" || el.Kind.ToString() == "vsCMElementNamespace")
{
CodeItem ci = new CodeItem(el);
rootPoolItem.CodeItems.Add(ci);
}
}
PoolList.Add(rootPoolItem);
}
}
InitializeComponent();
this.treeView1.DataContext = this;
}
}
And Here is a XAML code:
<UserControl x:Class="******.OpjectPool"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Height="Auto" Name="maingrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0" Height="Auto" HorizontalAlignment="Stretch" Name="scrollViewer1" VerticalAlignment="Stretch" Width="Auto" HorizontalScrollBarVisibility="Visible">
<TreeView Height="Auto" Name="treeView1" Width="Auto" ItemsSource="{Binding PoolList}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding CodeItems}" >
<TreeViewItem Header="{Binding Label}"/>
</HierarchicalDataTemplate >
</TreeView.ItemTemplate>
</TreeView>
</ScrollViewer>
<ScrollViewer Grid.Column="1" Height="Auto" HorizontalAlignment="Stretch" Name="scrollViewer2" VerticalAlignment="Stretch" Width="Auto" HorizontalScrollBarVisibility="Visible" Grid.ColumnSpan="1">
<DataGrid AutoGenerateColumns="False" Height="Auto" Name="dataGrid1" Width="Auto" FrozenColumnCount="3" />
</ScrollViewer>
<ScrollViewer Grid.Column="2" Height="Auto" HorizontalAlignment="Stretch" Name="scrollViewer3" VerticalAlignment="Stretch" Width="Auto" HorizontalScrollBarVisibility="Visible" Grid.ColumnSpan="1">
<DataGrid AutoGenerateColumns="False" Height="Auto" Name="dataGrid2" Width="Auto" />
</ScrollViewer>
<GridSplitter Grid.Column="1" Name="gridSplitter1" ResizeDirection="Columns" BorderBrush="Black" Background="Black" Margin="0,0,0,0" Grid.ColumnSpan="1" HorizontalAlignment="Left" Width="2" />
<GridSplitter Grid.Column="2" Name="gridSplitter2" ResizeDirection="Columns" BorderBrush="Black" Background="Black" Margin="0,0,0,0" Grid.ColumnSpan="1" HorizontalAlignment="Left" Width="2" />
</Grid>
PoolList is not null and contains a full hierarchical structure I need to bind.
Interesting that error message says about real and not null property of the object
As error states:
BindingExpression path error: 'PoolList' PROPERTY not found on 'object' ''OpjectPool' (Name='')'. BindingExpression:Path=PoolList;
PoolList is not a property.

listBox DataTemplate not picking up values

I am learning to use listBox in WPF with dataTemplate using the examples from MSDN, I can render a listBox bound to an ObservableCollection as a source and by overriding the ToString method.
However, I need to render an image and some texblocks for every item. Here's my XAML:
<Grid x:Class="MyAddin.WPFControls"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:c="clr-namespace:MyAddin"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Background="Transparent"
HorizontalAlignment="Stretch" Width="auto"
Height="215" VerticalAlignment="Stretch" ShowGridLines="False">
<Grid.Resources>
<c:People x:Key="MyFriends"/>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Left"
IsManipulationEnabled="True"
Height="20" Width="300">Activity Feed</TextBlock>
<ListBox Grid.Row="1" Name="listBox1" IsSynchronizedWithCurrentItem="True"
BorderThickness="0" ScrollViewer.VerticalScrollBarVisibility="Auto"
VerticalContentAlignment="Stretch" Margin="0,0,0,5" Background="Transparent">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Margin="5" BorderBrush="Black" BorderThickness="1">
<Image Source="{Binding Path=Avatar}" Stretch="Fill" Width="50" Height="50" />
</Border>
<StackPanel Grid.Column="1" Margin="5">
<StackPanel Orientation="Horizontal" TextBlock.FontWeight="Bold" >
<TextBlock Text="{Binding Path=Firstname }" />
</StackPanel>
<TextBlock Text="{Binding Path=Comment}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
My Collection class is as following:
public class People : ObservableCollection<Person>
{ }
public class Person
{
private string firstName;
private string comment;
private Bitmap avatar;
public Person(string first, string comment, Bitmap avatar)
{
this.firstName = first;
this.comment = comment;
this.avatar = avatar;
}
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string Comment
{
get { return comment; }
set { comment = value; }
}
public Bitmap Avatar
{
get { return avatar;}
set { avatar = value; }
}
public override string ToString()
{
return firstName.ToString();
}
}
Once my addin is loaded, I am downloading my data and setting the itemsSource.
People p = new People();
p.Add(new Person("Willa", "Some Comment", myAvatar));
p.Add(new Person("Isak", "Some Comment", myAvatar));
p.Add(new Person("Victor", "Some Comment", myAvatar));
this.wpfControl.listBox1.ItemsSource = p;
The problem I am facing is that the items are being rendered as empty rows whereas If I remove the dataTemplate, the items are rendered fine with their firstName.
Don't see anything wrong with the bindings themselves, but your avatar type seems off, WPF expects ImageSource (i do not know if there is any implicit convertion between Bitmap and ImageSource, check for binding errors to find out).

Textbox binding to list won't update

HI,
I have a list of coursesTaken with dates (returned by a nested class). Since I am not sure howmany courses the person took, I just bind it to textboxes and is working fine. The datacontext of the grid is the list itself so I assume whenever I make changes to the form, it is automatically sent back to the list and add it, but when it is time to save it in DB, the list won't change. I tried making the list as an observable collection and bind mode two way but still won't change. when I try to put Onpropertychange it says "Cannot acces a non static member of outer type via nested type..." What I want to do is whenever I type / add something to textbox, the list will add it as it's nth item so I can iterate and save it in DB.
is ther any other way to do this? please see code below. thanks!
#region class for binding purposes (nested class)
public class ListCon
{
public ObservableCollection<tblGALContinuingEducationHistory> EducList
{
get
{
return new ObservableCollection<tblGALContinuingEducationHistory>(contEducHstoryList);
}
}
}
#endregion
#region constructor
public ContinuingEducHistoryPopUp(tblAttorneyGalFileMaint currentGal)
{
contEducHstoryList = new ObservableCollection<tblGALContinuingEducationHistory>();
InitializeComponent();
if (currentGal != null)
{
CurrentGal = currentGal;
txtMemberName.Text = CurrentGal.FullName;
contEducHstoryList = new ObservableCollection<tblGALContinuingEducationHistory>(currentGal.tblGALContinuingEducationHistories.Count > 0 && currentGal.tblGALContinuingEducationHistories != null ? currentGal.tblGALContinuingEducationHistories : null);
}
this.DataContext = new ListCon();
}
#endregion
Now here's my xaml
<Grid Grid.Row="2" Grid.ColumnSpan="2" Name="grdEducForm">
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding Path=EducList[0].CourseTitle}" Grid.Column="0" Grid.Row="0" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[0].CertificateDate}" Grid.Column="1" Grid.Row="0" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[0].CertificateExpiration}" Grid.Column="2" Grid.Row="0" />
<TextBox Text="{Binding EducList[1].CourseTitle}" Grid.Column="0" Grid.Row="1" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[1].CertificateDate}" Grid.Column="1" Grid.Row="1" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[1].CertificateExpiration}" Grid.Column="2" Grid.Row="1" />
<TextBox Text="{Binding Path=EducList[2].CourseTitle}" Grid.Column="0" Grid.Row="2" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[2].CertificateDate}" Grid.Column="1" Grid.Row="2" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[2].CertificateExpiration}" x:Name="dpEnd3"
Grid.Column="2" Grid.Row="2" />
<TextBox Text="{Binding Path=EducList[3].CourseTitle, Mode=TwoWay}" Grid.Column="0" Grid.Row="3" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[3].CertificateDate, Mode=TwoWay}" Grid.Column="1"
Grid.Row="3" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[3].CertificateExpiration}" Grid.Column="2" Grid.Row="3" />
<TextBox Text="{Binding Path=EducList[4].CourseTitle}" Name="txtEducName5" Grid.Column="0" Grid.Row="4" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[4].CertificateDate}" x:Name="dpStart5" Grid.Column="1"
Grid.Row="4" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[4].CertificateExpiration}" x:Name="dpEnd5"
Grid.Column="2" Grid.Row="4" />
</Grid>
In your ListCon property you always return a new collection this defeats the point of having an ObservableCollection at all, you should store it in a field like this:
//Initialize field in constructor
private readonly ObservableCollection<tblGALContinuingEducationHistory> _ListCon;
public ObservableCollection<tblGALContinuingEducationHistory> ListCon
{
get
{
return _ListCon;
}
}
EducList = new ObservableCollection<tblGALContinuingEducationHistory>(currentGal.tblGALContinuingEducationHistories.Count > 0 && currentGal.tblGALContinuingEducationHistories != null ? currentGal.tblGALContinuingEducationHistories : null);
int count = 0;
//5 is the maximum no of course an atty can take and save in this form
count = 5 - EducList.Count;
for (int i = 0; i < count; i++)
{
galContEdu = FileMaintenanceBusiness.Instance.Create<tblGALContinuingEducationHistory>();
galContEdu.AttorneyID = currentGal.AttorneyID;
EducList.Add(galContEdu);
}
then save only the ones that has data:
foreach (var item in EducList)
{
if(!string.IsNullOrEmpty(item.CourseTitle) || !string.IsNullOrWhiteSpace(item.CourseTitle))
FileMaintenanceBusiness.Instance.SaveChanges(item, true);
}

Resources