I'm new to WPF, so please be patient with me. I have a bidimensional int array (8x8), a uniform grid (8 rows and 8 columns). In each cell of my grid there is a CheckBox. When I click on a checkbox I wish that the corresponding element in my array to change from "0" to "1". When I uncheck again the CheckBox, I wish that this change be reflected in my array.
Further, I will take each row from in my int[8,8] matrix (for example 10010101 - first checkbox in the uniform grid is checked, the second is not checked and so on), convert it to a decimal number and send it over the serial port when I click a button.
Then I want to change the sending process ( process all 8 rows and send the data) in such way that it takes place every time I click on a checkbox from my grid.
I'm stucked with the Bindings.
I hope someone can give me an advice on how should I to do this.
This is my XAML code. Somehow I succeeded to make a binding for my first CheckBox but I'm not understanding well why it's working and If I did the best coding. If I continue in this way I should create a property for every element in my array and I'm sure this is not the right way to do it (because I hardcoded data[0,0]).
<Window x:Class="CheckBox_Matrix_Binding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="220" Width="200" ResizeMode="NoResize" >
<Grid x:Name="mainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="4*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<UniformGrid x:Name="checkBoxGrid" Grid.Row="0" Rows="3" Columns="3" >
<CheckBox x:Name="ChekBox0" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding Mode=TwoWay, Path=DataProperty}" />
<CheckBox x:Name="ChekBox1" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox x:Name="ChekBox2" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox x:Name="ChekBox3" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox x:Name="ChekBox4" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox x:Name="ChekBox5" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox x:Name="ChekBox6" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox x:Name="ChekBox7" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox x:Name="ChekBox8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</UniformGrid>
<Grid Grid.Row="1" x:Name="buttonsGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="btnSetTrue" VerticalAlignment="Center" Grid.Column="0" Content="Set _True" Margin="5" Click="btnSetTrue_Click" ></Button>
<Button x:Name="btnSetFalse" VerticalAlignment="Center" Grid.Column="1" Content="Set _False" Margin="5" Click="btnSetFalse_Click" ></Button>
</Grid>
</Grid>
</Window>
This is my code behind:
using System;
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
namespace CheckBox_Matrix_Binding
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private DataMatrix _dataMatrix = new DataMatrix() ;
public bool _DataMatrixProperty
{
get { return _dataMatrix.DataProperty; }
set
{
_dataMatrix.DataProperty = value;
}
}
public MainWindow()
{
InitializeComponent();
checkBoxGrid.DataContext = _dataMatrix;
}
private void ChekBox0_Checked(object sender, RoutedEventArgs e)
{
}
private void btnSetTrue_Click(object sender, RoutedEventArgs e)
{
_dataMatrix.DataProperty = true;
}
private void btnSetFalse_Click(object sender, RoutedEventArgs e)
{
_dataMatrix.DataProperty = false;
}
}
//THe class DataMatrix implements INotifyPropertyChanged interface
public class DataMatrix: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool[,] data;
public bool DataProperty //this is a property
{
get { return data[0, 0]; }
set {
data[0, 0] = value;
OnPropertyChanged("DataProperty");
}
}
public DataMatrix()
{
data = new bool[3, 3];
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Should I use another approach?
Thankyou in advance :)
Related
I try to figure out how MVVM works and want to start simple.
I made a Model, a View and a ViewModel. I hooked up the view to a window. this works more or less.
in the view i have a textbox which i want to fill with the value of a property. The textbox keeps empty!?
This is what i have:
Model:
namespace Qbox_0001.Model
{
public class RegistratieModel
{
}
public class Registratie : INotifyPropertyChanged
{
//Fields
private string programmaNaam;
//eventhandler die kijkt of een property wijzigt
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
//Properies
public string ProgrammaNaam
{
get { return programmaNaam; }
set
{
if (programmaNaam != value)
{
programmaNaam = value;
RaisePropertyChanged("ProgrammaNaam");
}
}
}
}
}
The View:
<UserControl x:Name="UserControlRegistratie" x:Class="Qbox_0001.Views.RegistratieView"
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:local="clr-namespace:Qbox_0001.Views"
mc:Ignorable="d" Height="1000" Width="860" MaxWidth="860" HorizontalContentAlignment="Left" VerticalContentAlignment="Top">
<Grid x:Name="GridRegistratie">
<Grid.RowDefinitions>
<RowDefinition x:Name="RowDef_Menu" Height="21*" MaxHeight="21" MinHeight="21"/>
<RowDefinition x:Name="RowDef_TabControl" Height="500*" MinHeight="500"/>
<RowDefinition x:Name="Rowdef_Bottom" Height="40*" MaxHeight="40" MinHeight="40"/>
</Grid.RowDefinitions>
<Grid x:Name="Grid_Registratie_WorkArea" Grid.Row="1">
<TabControl x:Name="TabControl_Registratie">
<TabItem Header="Registratie">
<Grid x:Name="Grid_Tab_Registratie">
<Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition x:Name="GridRowDef_Algemeen" MinHeight="68" Height="68*" MaxHeight="68"/>
<RowDefinition x:Name="GridRowDef_Locatie" MinHeight="120" Height="120*" MaxHeight="120"/>
<RowDefinition x:Name="GridRowDef_AantalDagen" MinHeight="105" Height="105*" MaxHeight="105"/>
<RowDefinition x:Name="GridRowDef_MaxDagen" MinHeight="105" Height="105*" MaxHeight="105"/>
<RowDefinition x:Name="GridRowDef_Lokaal" MinHeight="100" Height="100*" MaxHeight="100"/>
<RowDefinition x:Name="GridRowDef_LicBestand" Height="150*" MaxHeight="150" MinHeight="150"/>
</Grid.RowDefinitions>
<GroupBox x:Name="GroupBox_algemeen" Header="Algemeen" Margin="10,4,10,3">
<Grid>
<Label x:Name="Label_Klant" Content="Klant:" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Padding="0,5,5,5"/>
<Label x:Name="Label_Programma" Content="Programma:" HorizontalAlignment="Left" Margin="356,10,0,0" VerticalAlignment="Top"/>
<Label x:Name="Label_Versie" Content="Versie:" HorizontalAlignment="Left" Margin="645,10,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="textBox_KlantNaam" HorizontalAlignment="Left" Height="23" Margin="49,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="288"/>
<!-- the textbox keeps empty -->
<TextBox x:Name="TextBox_ProgrammaNaam" Text="{Binding ElementName=RegistratieViewModel, Path=ProgrammaNaam, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="23" Margin="431,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="190" IsEnabled="False" />
<TextBox x:Name="TextBox_Versie" HorizontalAlignment="Left" Height="23" Margin="695,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" IsEnabled="False" />
</Grid>
</GroupBox>
</Grid>
</Border>
</Grid>
</TabItem>
<TabItem Header="Licentie(s)">
<Grid x:Name="Grid_Tab_Licentie" Background="#FFE5E5E5"/>
</TabItem>
</TabControl>
</Grid>
</Grid>
</UserControl>
In the View.cs:
namespace Qbox_0001.Views
{
/// <summary>
/// Interaction logic for RegistratieView.xaml
/// </summary>
public partial class RegistratieView : UserControl
{
public RegistratieView()
{
InitializeComponent();
this.DataContext = new Qbox_0001.ViewModel.RegistratieViewModel();
}
}
}
The ViewModel
using Qbox_0001.Model; //
using System.Collections.ObjectModel; //
namespace Qbox_0001.ViewModel
{
public class RegistratieViewModel
{
public RegistratieViewModel()
{
loadRegistratieOnderdelen();
}
public ObservableCollection<Registratie> RegistratieOnderdelen //Registratie = "public class Registratie : INotifyPropertyChanged" van de Model
{
get;
set;
}
public void loadRegistratieOnderdelen()
{
ObservableCollection<Registratie> regOnderdelen = new ObservableCollection<Registratie>();
regOnderdelen.Add(new Registratie { ProgrammaNaam = "Test" });
}
}
}
I can see a couple of problems with your code.
You are populating a local ObservableCollection (inside your loadRegistratieOnderdelen() method) with data but since its local, it is not a member of the DataContext and hence unavailable to the View. You have to use public properties like RegistratieOnderdelen which you already declared in your RegistratieViewModel.
Next you are using an ObservableCollection whereas you might just want to use a property of type String. Collections are used when you want to represent lists, for example inside a ListView or an ItemsControl. Your view indicates that you want to bind a single text value so a public property of type String makes more sense.
Best, Nico
The DataContext is a RegistratieViewModel. This class has a RegistratieOnderdelen property that returns a collection of Registratie objects.
You could bind to the ProgrammaNaam property of one such item but you need to specify which item to bind to, for example the first one:
<TextBox x:Name="TextBox_ProgrammaNaam" Text="{Binding Path=RegistratieOnderdelen[0].ProgrammaNaam, UpdateSourceTrigger=PropertyChanged}" ... />
I want to print listbox item count alog with header of expander like
header1 (count) . How can I achieve this in WPF
You haven't provided any code for what you have done, such as if you are using a ViewModel or doing code-behind. There are multiple ways to go about it.
My ViewModel Way
In this example, I've made a ViewModel containing an ObservableCollection of string to populate the ListBox. The Expander header is bound to a property that is populated using a combination of the HeaderText and ItemCount.
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _headerText = string.Empty;
private string _headerTextFull = string.Empty;
private ObservableCollection<string> _listItems = new ObservableCollection<string>();
private int _itemCount = 0;
public ViewModel() { }
public string HeaderText
{
get { return _headerText; }
set
{
_headerText = value;
NotifyPropertyChanged("HeaderText");
UpdateHeader();
}
}
public string HeaderTextFull
{
get { return _headerTextFull; }
set
{
_headerTextFull = value;
NotifyPropertyChanged("HeaderTextFull");
}
}
public ObservableCollection<string> ListItems
{
get { return _listItems; }
set
{
_listItems = value;
NotifyPropertyChanged("ListItems");
ItemCount = (_listItems != null ? _listItems.Count : 0);
}
}
public int ItemCount
{
get { return _itemCount; }
set
{
_itemCount = value;
NotifyPropertyChanged("ItemCount");
UpdateHeader();
}
}
private void UpdateHeader()
{
HeaderTextFull = String.Format("{0} ({1})", _headerText, _itemCount);
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The XAML for the Window:
<Window x:Class="SO37192142.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Expander Grid.Row="0" Name="expander1" Header="{Binding Path=HeaderTextFull, FallbackValue='Items'}" IsExpanded="True">
<Grid>
<ListBox Name="listBox1" Width="Auto" Height="Auto" ItemsSource="{Binding Path=ListItems}" />
</Grid>
</Expander>
<Button Grid.Row="1" Content="Add an Item" Click="Button_Click" />
</Grid>
</Window>
The code-behind:
public partial class Window1 : Window
{
ViewModel myModel = new ViewModel();
public Window1()
{
InitializeComponent();
myModel.ListItems.CollectionChanged += new NotifyCollectionChangedEventHandler(ListItems_CollectionChanged);
myModel.HeaderText = "Items";
myModel.ListItems.Add("Item 1");
myModel.ListItems.Add("Item 2");
this.DataContext = myModel;
}
void ListItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
myModel.ItemCount = myModel.ListItems.Count;
}
void Button_Click(object sender, RoutedEventArgs e)
{
myModel.ListItems.Add("Another item");
}
}
When it starts, the Expander header will say "Items (2)". Every time the button is clicked, the header will update to show the new count.
Slight Variation of Above
Here's an example that provides the above example, but also adds a second list to demonstrate a different way. Notice the Expander.Header section.
<Window x:Class="SO37192142.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Expander Grid.Row="0" Grid.Column="0" Name="expander1" Header="{Binding Path=HeaderTextFull, FallbackValue='Items'}" IsExpanded="True">
<Grid>
<ListBox Name="listBox1" Width="Auto" Height="Auto" ItemsSource="{Binding Path=ListItems}" />
</Grid>
</Expander>
<!-- SECOND EXPANDER THAT DOESN'T RELY ON A VIEWMODEL -->
<Expander Grid.Row="0" Grid.Column="1" Name="expander2" IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ElementName=listBox2, Path=Items.Count, UpdateSourceTrigger=PropertyChanged, StringFormat={}Items ({0})}" />
</StackPanel>
</Expander.Header>
<Grid>
<ListBox Name="listBox2" Width="Auto" Height="Auto" ItemsSource="{Binding Path=ListItems}" />
</Grid>
</Expander>
<Button Grid.Row="1" Grid.ColumnSpan="2" Content="Add an Item" Click="Button_Click" />
</Grid>
</Window>
Only Code Behind
If, for some reason, you are just using code-behind, you can do it this way:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
((INotifyCollectionChanged)listBox1.Items).CollectionChanged += new NotifyCollectionChangedEventHandler(ListBox_CollectionChanged);
}
void ListBox_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
expander1.Header = "Items (" + listBox1.Items.Count + ")";
}
}
I am developing a small application for learning purpose. I find that when I bind ItemControl's ItemSource to a ViewModel property in XAML, it doesn't work in an expected way. i.e. It loads the underlying collection with values at the loading time, but any changes to it are not reflected.
However, if I set Itemsource in Codebehind, it works.
When the form is loaded, it shows 2 note objects. Clicking on button should show the 3rd one. I don't understand why setting DataContext using XAML doesn't update to changes in collection. I am sharing snippet of the code here. Any help greatly appreciated.
Cut-down version of XAML -
<Window x:Class="NotesApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NotesApp"
xmlns:vm="clr-namespace:NotesApp.ViewModel"
Title="MainWindow" Height="480" Width="640">
<Window.DataContext >
<vm:MainViewModel/>
</Window.DataContext>
<DockPanel >
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl Name="NoteItemControl" ItemsSource="{Binding notes}" Background="Beige" >
<ItemsControl.LayoutTransform>
<ScaleTransform ScaleX="{Binding Value, ElementName=zoomSlider}" ScaleY="{Binding Value, ElementName=zoomSlider}" />
</ItemsControl.LayoutTransform>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Name="NoteBorder" Background="Green" CornerRadius="3" Margin="5,3,5,3">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding noteText}" Margin="5,3,5,3"/>
<StackPanel Grid.Row="1" Orientation="Vertical" >
<Line X1="0" Y1="0" X2="{Binding ActualWidth,ElementName=NoteBorder}" Y2="0" Stroke="Black" StrokeThickness="1"/>
<TextBlock Text="{Binding Category}" Margin="5,3,5,3"/>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
</Window>
View Code behind-
namespace NotesApp
{
public partial class MainWindow : Window
{
MainViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new MainViewModel();
// IT WORKS IF I BRING IN THIS STATEMENT
//NoteItemControl.ItemsSource = ViewModel.notes;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
ViewModel.AddNote(new Note("note3", "Category 3"));
}
}
}
ViewModel -
namespace NotesApp.ViewModel
{
public class MainViewModel: INotifyPropertyChanged
{
ObservableCollection<Note> _notes;
public ObservableCollection<Note> notes
{
get
{ return _notes; }
set
{
_notes = value;
OnPropertyChanged("notes");
}
}
public void AddNote(Note note)
{
_notes.Add(note);
OnPropertyChanged("notes");
}
public MainViewModel ()
{
notes = new ObservableCollection<Note>();
notes.Add(new Note("note1", "Category 1"));
notes.Add(new Note("note2", "Category 2"));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs( propertyName));
}
}
}
You create a MainViewModel instance and assign it to the MainWindow's DataContext in XAML
<Window.DataContext >
<vm:MainViewModel/>
</Window.DataContext>
The bindings in your XAML use this instance as their source object, as long as you do not explicitly specify some other source. So there is no need (and it's an error) to create another instance in code behind.
Change the MainWindow's constructor like this:
public MainWindow()
{
InitializeComponent();
ViewModel = (MainViewModel)DataContext;
}
Try this :
<Window.Resources>
<vm:MainViewModel x:Key="mainVM"/>
</Window.Resources>
Now use this key as a static resource wherever you bind something like :
<ItemsControl Name="NoteItemControl" ItemsSource="{Binding notes,Source={StaticResource mainVM},Mode=TwoWay}" Background="Beige" >
If you do this, you dont need any datacontext
I have a DataBinding on a ListBox, bound to an ObservableCollection. Debugging at runtime shows the ObservableCollection does have items in it, and they're not null. My code all looks fine, however for some reason nothing is being displayed in my ListBox. It definitely was working previously, however it no longer is - and I can't figure out why. I've examined previous versions of the code and found no differences that would have any effect on this - minor things like Width="Auto" etc.
I based my code off of the example found here:
http://msdn.microsoft.com/en-us/library/hh202876.aspx
So, my code:
XAML:
<phone:PhoneApplicationPage
x:Class="MyNamespace.MyItemsListPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
shell:SystemTray.IsVisible="True">
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" x:Name="PageTitle" Text="MyPageTitle" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<!-- Bind the list box to the observable collection. -->
<ListBox x:Name="myItemsListBox" ItemsSource="{Binding MyItemsList}" Margin="12, 0, 12, 0" Width="440">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch" Width="440">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Text="{Binding MyItemNumber}"
FontSize="{StaticResource PhoneFontSizeLarge}"
Grid.Column="0"
VerticalAlignment="Center"
Margin="0,10"
Tap="TextBlock_Tap"/>
<TextBlock
Text="{Binding MyItemName}"
FontSize="{StaticResource PhoneFontSizeLarge}"
Grid.Column="1"
VerticalAlignment="Center"
Margin="0,10"
Tap="TextBlock_Tap" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
</phone:PhoneApplicationPage>
C#:
namespace MyNamespace
{
public partial class MyItemsListPage : PhoneApplicationPage, INotifyPropertyChanged
{
private static ObservableCollection<MyItem> _myItemsList;
private ObservableCollection<MyItem> MyItemsList
{
get
{
return _myItemsList;
}
set
{
if (_myItemsList!= value)
{
_myItemsList= value;
NotifyPropertyChanged("MyItemsList");
}
}
}
public MyItemsListPage ()
{
InitializeComponent();
this.DataContext = this;
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
HelperClass helper = new HelperClass();
MyItemsList = helper.GetItems(this.NavigationContext.QueryString["query"]);
base.OnNavigatedTo(e); // Breakpoint here shows "MyItemsList" has MyItem objects in it.
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify Silverlight that a property has changed.
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
The Helper class is a connector to my read-only local database on the device. It returns an ObservableCollection<MyItem>:
public ObservableCollection<MyItem> GetItems(string itemName)
{
// Input validation etc.
// Selecting all items for testing
var itemsInDB =
from MyItem item in db.Items
select item;
return new ObservableCollection<MyItem>(itemsInDB);
}
And finally the MyItem class:
[Table]
public class MyItem: INotifyPropertyChanged, INotifyPropertyChanging
{
private int _myItemId;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int MyItemId
{
...
}
private string _myItemName;
[Column(CanBeNull = false)]
public string MyItemName
{
get
{
return _myItemName;
}
set
{
if (_myItemName!= value)
{
NotifyPropertyChanging("MyItemName");
_myItemName= value;
NotifyPropertyChanged("MyItemName");
}
}
}
private int _myItemNumber;
[Column]
public int MyItemNumber
{
get
{
return _myItemNumber;
}
set
{
if (_myItemNumber!= value)
{
NotifyPropertyChanging("MyItemNumber");
_myItemNumber= value;
NotifyPropertyChanged("MyItemNumber");
}
}
}
// Other properties, NotifyPropertyChanged method, etc...
}
This is rather frustrating as my DataBinding elsewhere in the application is working perfectly, so I've no idea why I can't get this to work.
The problem was that my ObservableCollection was private. Changing it to have a public access modifier allowed my ListBox to display the contents:
public ObservableCollection<MyItem> MyItemsList
Simply that you're binding to properties named incorrectly:
Text="{Binding ItemName}" should be Text="{Binding MyItemName}"
Notice you left out "My"
i have made a template that look like this :
<ControlTemplate x:Key="onoffValue" TargetType="{x:Type Control}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Height="20" Margin="0,5,0,0">
<RadioButton Content="On" Height="20" Name="On_radiobutton" />
<RadioButton Content="Off" Height="20" Name="Off_radiobutton" Margin="20,0,0,0" />
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=BootSector}" Value="true">
<Setter TargetName="On_radiobutton" Property="IsChecked" Value="true"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=BootSector}" Value="false">
<Setter TargetName="Off_radiobutton" Property="IsChecked" Value="true"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
For now, it is bind to the property BootSector(bool) ofa "Configuration" object.
I use this template in my window that has a configuration object as data context like this :
<Control Template="{StaticResource onoffValue}">
</Control>
It works great, but i want to go further.
I would like to know how i can pass a different property to my template to dynamically bind (dynamically change the property the template is bind to)
ie i tryed something like
<Control Template="{StaticResource onoffValue}" xmlns:test="{Binding Path=BootSector}"/>
and bind it in the template to "test" but it doesn't work
Is it possible ? How can i do that ? I think i'm not too far away but not there still !
Thank you in advance
Edit : Concerning Dmitry answer :
There is a bug using that. When i do :
<StackPanel local:ToggleControl.IsOn="{Binding BootSector, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal" HorizontalAlignment="Center" Margin="5">
<RadioButton Content="On" local:ToggleControl.Role="On" Height="20" Margin="5" />
<RadioButton Content="Off" local:ToggleControl.Role="Off" Height="20" Margin="5" />
</StackPanel>
By default BootSector is on false. When i click on the on button (true), it sets bootSector to true and then immediately to false . The behaviour should be that it stays to true until it is unchecked ? Is this related to the problem related here ? http://geekswithblogs.net/claraoscura/archive/2008/10/17/125901.aspx
Here, the idea is - generic behaviors are never complex and generally not worth creating a custom control. I undertand that implmentation may vary, but the approach will remain the same. It makes sense to use XAML for the parts which can change and code for the stuff which will remain constant.
UPDATE 1- It's getting even easier when using Custom controls. You won't need attached property no more - as you'll get a dedicated space for it inside your custom control, also, you can use x:Name and GetTemplateChild(..) to otain a reference to individual RadioButtons.
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace RadioButtons
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += (o, e) =>
{
this.DataContext = new TwoBoolean()
{
PropertyA = false,
PropertyB = true
};
};
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(((TwoBoolean)this.DataContext).ToString());
}
}
public enum RadioButtonRole
{
On,
Off
}
public class ToggleControl : DependencyObject
{
public static readonly DependencyProperty IsOnProperty =
DependencyProperty.RegisterAttached("IsOn",
typeof(bool?),
typeof(ToggleControl),
new PropertyMetadata(null,
new PropertyChangedCallback((o, e) =>
{
ToggleControl.OnIsOnChanged((Panel)o, (bool)e.NewValue);
})));
public static readonly DependencyProperty RoleProperty =
DependencyProperty.RegisterAttached("Role",
typeof(RadioButtonRole?),
typeof(ToggleControl),
new PropertyMetadata(null,
new PropertyChangedCallback((o, e) =>
{
})));
private static readonly DependencyProperty IsSetUpProperty =
DependencyProperty.RegisterAttached("IsSetUp",
typeof(bool),
typeof(ToggleControl),
new PropertyMetadata(false));
private static void OnIsOnChanged(Panel panel, bool e)
{
if (!ToggleControl.IsSetup(panel))
{
ToggleControl.Setup(panel);
}
RadioButtonRole role;
if (e)
{
role = RadioButtonRole.On;
}
else
{
role = RadioButtonRole.Off;
}
ToggleControl.GetRadioButtonByRole(role, panel).IsChecked = true;
}
private static void Setup(Panel panel)
{
// get buttons
foreach (RadioButton radioButton in
new RadioButtonRole[2]
{
RadioButtonRole.On,
RadioButtonRole.Off
}.Select(t =>
ToggleControl.GetRadioButtonByRole(t, panel)))
{
radioButton.Checked += (o2, e2) =>
{
RadioButton checkedRadioButton = (RadioButton)o2;
panel.SetValue(ToggleControl.IsOnProperty,
ToggleControl.GetRadioButtonRole(checkedRadioButton) == RadioButtonRole.On);
};
}
panel.SetValue(ToggleControl.IsSetUpProperty, true);
}
private static bool IsSetup(Panel o)
{
return (bool)o.GetValue(ToggleControl.IsSetUpProperty);
}
private static RadioButton GetRadioButtonByRole(RadioButtonRole role,
Panel container)
{
return container.Children.OfType<RadioButton>().First(t =>
(RadioButtonRole)t.GetValue(ToggleControl.RoleProperty) == role);
}
private static RadioButtonRole GetRadioButtonRole(RadioButton radioButton)
{
return (RadioButtonRole)radioButton.GetValue(ToggleControl.RoleProperty);
}
public static void SetIsOn(DependencyObject o, bool? e)
{
o.SetValue(ToggleControl.IsOnProperty, e);
}
public static bool? GetIsOn(DependencyObject e)
{
return (bool?)e.GetValue(ToggleControl.IsOnProperty);
}
public static void SetRole(DependencyObject o, RadioButtonRole? e)
{
o.SetValue(ToggleControl.RoleProperty, e);
}
public static RadioButtonRole? GetRole(DependencyObject e)
{
return (RadioButtonRole?)e.GetValue(ToggleControl.RoleProperty);
}
}
public class TwoBoolean: INotifyPropertyChanged
{
private bool propertyA, propertyB;
public bool PropertyA
{
get
{
return this.propertyA;
}
set
{
this.propertyA = value;
this.OnPropertyChanged("PropertyA");
}
}
public bool PropertyB
{
get
{
return this.propertyB;
}
set
{
this.propertyB = value;
this.OnPropertyChanged("PropertyB");
}
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
public override string ToString()
{
return string.Format("PropertyA:{0}, PropertyB:{1}", this.PropertyA, this.PropertyB);
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Markup:
<Window x:Class="RadioButtons.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RadioButtons"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Margin="5" VerticalAlignment="Center">PropertyA</TextBlock>
<StackPanel local:ToggleControl.IsOn="{Binding PropertyA, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal" HorizontalAlignment="Center" Margin="5">
<RadioButton Content="On" local:ToggleControl.Role="On" Height="20" Margin="5" />
<RadioButton Content="Off" local:ToggleControl.Role="Off" Height="20" Margin="5" />
</StackPanel>
<TextBlock Grid.Row="1" Grid.Column="0" Margin="5" VerticalAlignment="Center">PropertyB</TextBlock>
<StackPanel local:ToggleControl.IsOn="{Binding PropertyB, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Grid.Row="1" Grid.Column="1"
Orientation="Horizontal" HorizontalAlignment="Center" Margin="5">
<RadioButton Content="On" local:ToggleControl.Role="On" Height="20" Margin="5" />
<RadioButton Content="Off" local:ToggleControl.Role="Off" Height="20" Margin="5" />
</StackPanel>
<Button Click="Button_Click" Grid.Row="3" Grid.ColumnSpan="2">Save</Button>
</Grid>
</Window>
You should not use an xmlns to pass a parameter, rather use the Tag or template a ContentControl, then you can bind the Content to your property (set it to TwoWay) and use a TemplateBinding to Content inside the template.