I'm having yet another WPF binding issue. Just when I think I've got this stuff figured out, I run into more problems... :S
Anyway... I've created a custom user control for selecting files. It's a simple textbox followed by a button contained within a grid. The property of the control with which I am working is called FilePath and the TextBox on this control is bound to that property. When the button is clicked, a SaveFileDialog is opened and the user selects a file. The UI correctly updates after the user selects the file.
The problem I seem to be having is that when I bind an object to the control (in this instance I have an object with a DocumentFilePath property) the object doesn't update when a new file is selected.
Here's the relevant code within my user control:
public static readonly DependencyProperty FilePathProperty = DependencyProperty.Register("FilePath", typeof(string), typeof(FileSave), new UIPropertyMetadata(string.Empty, OnFilePathChanged));
public string FilePath
{
get
{
return this.GetValue(FilePathProperty) as string;
}
set
{
this.SetValue(FilePathProperty, value);
this.OnPropertyChanged("FilePath");
}
}
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
private static void OnFilePathChanged(object sender, DependencyPropertyChangedEventArgs e)
{
((FileSave)sender).OnPropertyChanged("FilePath");
}
And the user control is added into my Window programatically by using reflection on my object:
private void AddFileSave(PropertyInfo pi)
{
FileSave fs = new FileSave();
Binding b = new Binding(pi.Name);
fs.SetBinding(FileSave.FilePathProperty, b);
this.AddToGrid(fs); //adds the control into my window's grid in the correct row and column; nothing fancy here
}
It may be worth noting that if I load the window with an existing object, my user control displays properly but still won't register any changes within the object to which it is bound.
Please let me know if you guys need any more info.
Thanks in advance,
Sonny
EDIT: I've found a way around the problem, but this probably isn't a good solution. By watching the debugger carefully I found that when I set the FilePath property within my control, the object was being unbound. If anyone can shed some light on that, I would be most appreciative. In the mean time, I've changed the code that opens my SaveFileDialog to look like this:
private void Button_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog ofd = new Microsoft.Win32.OpenFileDialog();
ofd.Multiselect = false;
ofd.Title = "Select document to import...";
ofd.ValidateNames = true;
ofd.ShowDialog();
if (this.GetBindingExpression(FilePathProperty) == null)
{
this.FilePath = ofd.FileName;
}
else //set value on bound object (THIS IS THE NEW PORTION I JUST ADDED)
{
BindingExpression be = this.GetBindingExpression(FilePathProperty);
string propName = be.ParentBinding.Path.Path;
object entity = be.DataItem;
System.Reflection.PropertyInfo pi = entity.GetType().GetProperty(propName);
pi.SetValue(entity, ofd.FileName, null);
}
if (!string.IsNullOrWhiteSpace(this.FilePath))
{
_fileContents = new MemoryStream();
using (StreamReader sr = new StreamReader(this.FilePath))
{
_fileContents = new MemoryStream(System.Text.ASCIIEncoding.ASCII.GetBytes(sr.ReadToEnd()));
}
}
else
{
_fileContents = null;
}
}
You're not specifying anywhere in your code that the FilePath property should be TwoWay so updates of the DP value won't get pushed to the bound source object's property. You can use either:
Binding b = new Binding(pi.Name){ Mode = BindingMode.TwoWay };
or you can set up your Dependency Property to use a default of TwoWay:
public static readonly DependencyProperty FilePathProperty = DependencyProperty.Register(
"FilePath", typeof(string), typeof(FileSave),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnFilePathChanged));
You should also follow Robert's suggestion of removing the manual PropertyChange event, and also don't EVER add any code other than GetValue and SetValue in your DP wrapper property. XAML calls GetValue and SetValue directly so will skip over anything else you add there - which can lead to very nasty bugs.
Why, yes! I most certainly can shed some light on that!
Also, if you're using .Net 4.0, today's your lucky day!
Consider the following fine method on your DependencyObject:
SetCurrentValue();
Yes! With this SINGULAR method, all your woes will drift away as a bad dream at the rooster's crow! (Well, ok, not really, but that is the method you're looking for.)
Short story very short: When you programmatically SetValue() on a control in your view layer, you blow away your bindings. SetCurrentValue() was added to the framework because you frequently want to drive a change in your bound object by setting that value directly. An alternate design would be to set the value in your bound object programmatically and let the updated value get pulled back into the view, but that's frequently clumsy.
(I strongly suspect that the absence of this method up to this point is largely responsible for the utter failure of the vast majority of NumericUpDown controls in WPF.)
First, you don't need to raise the PropertyChanged event when a dependency property changes; with dependency properties, change notification comes for free.
What's probably happening here: The default behavior for UpdateSourceTrigger is LostFocus, i.e. the source gets updated when the user presses TAB to move to the next field, or clicks on another control, or whatever. The text box isn't losing focus after your SaveFileDialog sets Text (since it probably doesn't even have the focus in the first place), so the source update never gets triggered.
To make it update the source whenever the Text property changes, set the UpdateSourceTrigger to PropertyChanged.
If that doesn't work, watch the Output window for binding errors.
Edit:
Here's a little prototype application I built. It works just fine: typing in the text box sets the property, clicking on the "Save" button sets the property, and the binding in the main window gets updated properly no matter what.
<Window x:Class="DependencyPropertyBindingDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:demo="clr-namespace:DependencyPropertyBindingDemo"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<demo:FilePicker x:Name="Picker"
DockPanel.Dock="Top"
Margin="5" />
<TextBox DockPanel.Dock="Top"
Text="{Binding ElementName=Picker, Path=FilePath}" />
<TextBlock />
</DockPanel>
</Window>
<UserControl x:Class="DependencyPropertyBindingDemo.FilePicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel>
<TextBox DockPanel.Dock="Left"
Width="200"
Text="{Binding FilePath, UpdateSourceTrigger=PropertyChanged}" />
<Button Width="50"
DockPanel.Dock="Left"
Command="{Binding Path=SaveCommand}">Save</Button>
<TextBlock />
</DockPanel>
</UserControl>
public partial class FilePicker : UserControl
{
public FilePicker()
{
SaveCommand = new FilePickerSaveCommand(this);
DataContext = this;
InitializeComponent();
}
public ICommand SaveCommand { get; set; }
public static readonly DependencyProperty FilePathProperty = DependencyProperty.Register("FilePath", typeof(string), typeof(FilePicker));
public string FilePath
{
get
{
return GetValue(FilePathProperty) as string;
}
set
{
SetValue(FilePathProperty, value);
}
}
}
public class FilePickerSaveCommand : ICommand
{
private FilePicker _FilePicker;
public FilePickerSaveCommand(FilePicker picker)
{
_FilePicker = picker;
}
public void Execute(object parameter)
{
_FilePicker.FilePath = "Testing";
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
Related
I have made a User Control, FontSelector, that groups together a ComboBox for FontFamily Selection and three ToggleButtons for Bold, Italics, Underline options. I am having an issue with the ComboBox's SelectedItem property affecting all instances of that User Control within the same Window. For example, changing the ComboBox selection on one, will automatically change the other. For Clarity. I don't want this behavior. I am very surprised that a User Control is implicitly affecting another User Control.
XAML
<Grid x:Name="Grid" Background="White" DataContext="{Binding RelativeSource={RelativeSource AncestorType=local:FontSelector}}">
<ComboBox x:Name="comboBox" Width="135"
SelectedItem="{Binding Path=SelectedFontFamily}" Style="{StaticResource FontChooserComboBoxStyle}"
ItemsSource="{Binding Source={StaticResource SystemFontFamilies}}"/>
</Grid>
Code Behind
The CLR Property that the ComboBox's SelectedItem is Bound to. Code shown here is in the User Control Code Behind File, not a ViewModel.
private FontFamily _SelectedFontFamily;
public FontFamily SelectedFontFamily
{
get
{
return _SelectedFontFamily;
}
set
{
if (_SelectedFontFamily != value)
{
_SelectedFontFamily = value;
// Modify External Dependency Property Value.
if (value != SelectedFont.FontFamily)
{
SelectedFont = new Typeface(value, GetStyle(), GetWeight(), FontStretches.Normal);
}
// Notify.
RaisePropertyChanged(nameof(SelectedFontFamily));
}
}
}
The Dependency Property that updates it's value based on the Value of the ComboBox's SelectedItem Property. It effectively packages the FontFamily value into a Typeface Object.
public Typeface SelectedFont
{
get { return (Typeface)GetValue(SelectedFontProperty); }
set { SetValue(SelectedFontProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedFont. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedFontProperty =
DependencyProperty.Register("SelectedFont", typeof(Typeface), typeof(FontSelector),
new FrameworkPropertyMetadata(new Typeface("Arial"), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnSelectedFontPropertyChanged)));
private static void OnSelectedFontPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as FontSelector;
var newFont = e.NewValue as Typeface;
if (newFont != null)
{
instance.SelectedFontFamily = newFont.FontFamily;
}
}
EDIT
I think I may have figured out what is going on. I can replicate it by Binding the ItemsSource to the Following Collection View Source.
<CollectionViewSource x:Key="SystemFontFamilies" Source="{Binding Source={x:Static Fonts.SystemFontFamilies}}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Source"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
You can then replicate the behavior by placing 2 ComboBoxes and Binding both of them to the CollectionViewSource. They will now, seemingly implicitly track each others SelectedItem. Even without Any Data Binding outside of ItemsSource. It would seem that the CollectionViewSource is somehow playing a part in what the SelectedItem is.
I'd make it a bit different. I'll introduce this solution using only a String, not FontFamily or FontWeight, since I have no VS here right now. (In order to have it working, please change the list of FontFamilies to a list of strings to bind them.)
Your selector UserControl:
- your xaml is ok (but you won't need the x:Name)
- the CodeBehind of the UserControl (later: UC) should change, we will solve it with binding. You should have a DependencyProperty, lets' call it SelectedFontFamily, which will represent the selected string from the ComboBox:
public string SelectedFontFamily
{
get { return (string)GetValue(SelectedFontFamilyProperty); }
set { SetValue(SelectedFontFamilyProperty, value); }
}
public static readonly DependencyProperty SelectedFontFamilyProperty = DependencyProperty.Register("SelectedFontFamily", typeof(string), typeof(YourUC), new PropertyMetadata(string.Empty));
The Window, which contains the UC:
- You should include the namespace of the UC's folder in the opening tag of the window, eg:
<Window
...
xmlns:view="clr-namespace:YourProjectName.Views.UserControls">
- the window's DataContext should have a property with public set option (feel free to implement INotifyPropertyChange on it):
public string FontFamily {get; set;}
- in the Window's xaml you would use the UC this way:
<view:YourUC SelectedFontFamily="{Binding FontFamily, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
It's a two-way binding. You'll find the selected string as the value of the FontFamily property every time you change the SelectedItem.
Edit: you will need View Model class for the Window which is using the UserControl. Create it, make it implement the INotifyPropertyChanged interface, and set it as DataContext for your consumer window. WPF is not like WF, you can find more about it if you Google up "WPF MVVM" or something like that.
Found the problem. I was binding to a CollectionViewSource defined in Application Resources. Until now I was unaware that Binding to a CollectionViewSource will also affect the SelectedItem. The SelectedItem Data gets stored as part of the CollectionViewSource. Setting the IsSynchronizedWithCurrentItem property to False on the ComboBox solved the issue.
Here is an existing answer I have now Found.
Thanks
I'm making a Universal application for Windows Phone 8.1 and have a problem with my code.
After TextBlock value become greater or equal than 22, some images should become visible. If the value is less than 22 all images should be invisible.
My question: How I can get visible images after textblock value >="22"
This is my code to hide images:
private void points_Loaded(object sender, RoutedEventArgs e)
{
int n = 0;
bool b = int.TryParse(points.Text, out n);
DataContext = this;
ImageVis = (b && n >= 22) ? Visibility.Visible : isibility.Collapsed;
}
private Visibility imageVis;
public Visibility ImageVis
{
get { return imageVis; }
set
{
imageVis = value;
RaisePropertyChanged("ImageVis");
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
This code part is from XAML:
<Image x:Name="hole17img"
HorizontalAlignment="Left"
Height="57"
Margin="10,3540,0,0"
VerticalAlignment="Top"
Width="380"
Source="Assets/septinpatsmit.png"
Stretch="Fill"
Visibility="{Binding ImageVis, Mode=TwoWay}"/>
I have problem with: RaisePropertyChanged("ImageVis");
The name 'RaisePropertyChanged' does not exist in the current context
Does this mean I have make some object with that name? or something else?
I can provide my My application so you can see what's happening.
My application sample
RaisePropertyChanged is MVVM Light's method and makes UI updated whenever you raise a property with the given name.In the XAML code behind , you bind ViewModel's properties to XAML properties and when RaisePropertyChanged triggers , it notifies the given property and UI is refreshed after.
You also need to use Converters to convert boolean to Visibility.In general , you need more MVVM Pattern knowledge to Windows projects.
Check out this post
http://www.mvvmlight.net/doc/
In my UserControl I have a Checkbox
<CheckBox DockPanel.Dock="Left" VerticalAlignment="Bottom" VerticalContentAlignment="Bottom" x:Name="showLegendsChk" Margin="10,0,0,0"
Content="View Legends" Checked="showLegendsChk_Checked" />
<!--IsChecked="{Binding ElementName=CrossSecViewWnd, Path=ShowLegends, Mode=TwoWay}" -->
I tried to add data binding to it, & added some logic on checked & non-checked; so no need to add event to the same.
private bool showLegendsWnd;
public CrossSectionalViewControl() {
FillLegends();
ShowLegends = false;
}
// Using a DependencyProperty as the backing store for
//IsCheckBoxChecked. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ShowLegendsProperty =
DependencyProperty.Register("ShowLegends", typeof(bool),
typeof(CrossSectionalViewControl), new UIPropertyMetadata(false));
public bool ShowLegends
{
get { return showLegendsWnd; }
set
{
showLegendsWnd = value;
NotifyPropertyChanged("ShowLegends");
if (showLegendsWnd == true)
legendWrap.Visibility = System.Windows.Visibility.Visible;
else
legendWrap.Visibility = System.Windows.Visibility.Hidden;
Console.WriteLine("Show Legends = " + showLegendsWnd + " Chk Value = " + showLegendsChk.IsChecked);
}
}
Tried alot wit binding, but no success. Finally added checked event & commented binding property. -
private void showLegendsChk_Checked(object sender, RoutedEventArgs e)
{
showLegendsWnd = (bool)showLegendsChk.IsChecked;
Console.WriteLine("CHK Show Legends = " + showLegendsWnd + " Chk Value = " + showLegendsChk.IsChecked);
if (showLegendsWnd == true)
legendWrap.Visibility = System.Windows.Visibility.Visible;
else
legendWrap.Visibility = System.Windows.Visibility.Hidden;
legendWrap.UpdateLayout();
}
With this also, even when checkbox is unChecked, it doesn't fire event nor property with both checked & unchecked.
In both - Binding & Event 1 state event is fired properly but the other isn't !!! Have also added TwoWay mode, tried with UpdateSourceTrigger in binding but no success.
Why is this Strange problem with Checkbox....
For your event:
You also need to subscribe to the Unchecked event.
Change your xaml to:
<CheckBox x:Name="showLegendsChk"
DockPanel.Dock="Left"
VerticalAlignment="Bottom"
VerticalContentAlignment="Bottom"
Margin="10,0,0,0"
Content="View Legends"
Checked="showLegendsChk_Checked"
Unchecked="showLegendsChk_Checked" />
Now both events will fire the same handler and if you set a breakpoint in your handler, you can see it get called.
For your binding
Not quite sure what you're trying with it. Firstly the property you define for your DP's are merely a convenience for you and the underlying framework does not invoke it's setter's or getter's when updating the DP value. Next up, not sure why you're calling a NotifyPropertyChanged("ShowLegends"); on a DP. If my assumtion is right and that actually corresponds to a INPC raise property changed implementation, it's not needed for DP's
Start with trying simple stuff. Something like:
<CheckBox x:Name="chkBox"
IsChecked="{Binding IsChecked}" />
and in your DataContext class have the corresponding property
private bool _isChecked;
public bool IsChecked {
get { return _isChecked; }
set {
_isChecked = value;
Debug.WriteLine(string.Format("Checkbox check state changed to: {0}", _isChecked ? "Checked" : "Not Checked"));
}
}
You should see the Debug.WriteLine get invoked on changes to the IsChecked property. once you got to that stage add the rest of your logic step by step and validate if it still works and if it doesn't, it's prolly something you're adding causing it than a behavior of the system.
Update:
Download Link to sample
Attached sample should show three approaches. Event based, Simple Binding, Complex Binding connected to a DP from a custom control to then toggle that control's visibility.
My program contains several instances of this line:
<local:MyClass Data="{Binding}"/>
I.e. the property Data is bound to the data context of the surrounding window. When the value of the window's DataContext changes, the binding is sometimes updated, sometimes not; it depends on the position of <local:MyClass...> in the XAML file.
Here is an example (EDIT: I changed the {Binding} to {Binding Path=DataContext, ElementName=myWindow} to emphasise that the problem is not related to inheritance of DataContext):
XAML code:
<Window x:Class="BindTest.MainWindow"
x:Name="myWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindTest"
Title="Binding Test" Height="101" Width="328">
<Window.Tag>
<local:MyClass x:Name="bindfails" Data="{Binding Path=DataContext, ElementName=myWindow}"/>
</Window.Tag>
<StackPanel Orientation="Horizontal">
<Button Margin="5" Padding="5" Click="SetButtonClicked">Set DataContext</Button>
<Button Margin="5" Padding="5" Click="ReadButtonClicked">Read Bound Property</Button>
<local:MyClass x:Name="bindworks" Data="{Binding Path=DataContext, ElementName=myWindow}"/>
</StackPanel>
</Window>
C# code:
using System.Windows;
namespace BindTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void SetButtonClicked(object sender, RoutedEventArgs e)
{
DataContext = 1234;
}
private void ReadButtonClicked(object sender, RoutedEventArgs e)
{
string txtA = (bindfails.Data == null ? "null" : bindfails.Data.ToString());
string txtB = (bindworks.Data == null ? "null" : bindworks.Data.ToString());
MessageBox.Show(string.Format("bindfails.Data={0}\r\nbindworks.Data={1}", txtA, txtB));
}
}
public class MyClass : FrameworkElement
{
#region Dependency Property "Data"
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(MyClass), new UIPropertyMetadata(null));
#endregion
}
}
First press button "Set DataContext" to change the data context. Then press button "Read Bound Property", which display this message:
bindfails.Data=null
bindworks.Data=1234
Obviously, the data binding was only updated for the MyClass element that is child of the StackPanel; but the data binding was not updated for the MyClass element that is referenced by Window.Tag.
EDIT2: I also have found out that binding works when adding the binding programmatically inside MainWindow's constructor:
Binding binding = new Binding("DataContext") {Source = this};
bindfails.SetBinding(MyClass.DataProperty, binding);
The binding only fails when it is declared in XAML. Furthermore, the problem is not specific to DataContext; it also happens when I use other Window properties, such as Title.
Can anyone explain this behavior and suggest how to allow the use of {Binding} in XAML in both cases?
EDIT3: The above code is not entirely equivalent to the {Binding} markup extension. The 100% equivalent code is:
Binding binding = new Binding("DataContext") {ElementName = "myWindow"};
bindfails.SetBinding(MyClass.DataProperty, binding);
When I use that code, binding also fails (like when binding in XAML) and the following diagnostics message is written to debug output:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=myWindow'.
Obviously, the ElementName property only searches up the visual tree or logical tree, even though this is not documented in the WPF online documentation. Probably, there is no easy way to set such a binding in XAML.
The dataContext is passed only throught the Object Tree. The Property tag is not in the visual tree and will not respond to DataContext changed Event and the binding is not refreshed without this event.
See :
Dependency property identifier field: DataContextProperty from the FrameworlElement
Data context is a concept that allows objects to inherit binding-specifying information from their parents in the object tree
http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.datacontext(v=vs.95).aspx
I suppose it does not work because when you write {Binding}, the context of the data to be bound is inherited from the parent. In case of bindworks, it's a StackPanel, which inherits DataContext from Window, however bindfails.Parent property is null.
I wonder why have you put a control in Window's tag element. If you must keep it declared in Tag node for some reason, you can update its DataContext directly, so just change SetButtonClicked method to:
private void SetButtonClicked(object sender, RoutedEventArgs e)
{
DataContext = 1234;
bindfails.DataContext = DataContext;
}
Another simple method to make it work is just taking bindfails out of Window.Tag and placing it somewhere in the Window, i.e. in the StackPanel.
Next, in the Window's constructor, write this.Tag = bindfails. If you don't want the TextBox to appear in the form, you can set its Visibility to Collapsed (or put it inside a collapsed container control).
I'm new to XAML and C#, but have been enjoying working with it in the couple of weeks I've played with it. I've started working on an App and have put together a simple "Settings" page in XAML; now I'm trying to hook up events to the controls to (a) update application state when the user interacts with them, and (b) have the current state upon visiting the page.
I've hit two (related) road-blocks though:
the toolkit:ListPicker control doesn't seem to work well when I define the "ListPickerItem"s in XAML, so in the SettingsPage constructor, I set the contents manually:
lpColour.ItemsSource = new List<string>()
{
"Red","Blue","Green","Custom…"
};
lpColour.SelectedIndex = 1; // set the currently selected item to "Blue"
However, because the control (lpColour in this example) has an Event on SelectionChanged, two events get fired (one with "Red" selected as the box is populated, then another when "Blue" is selected). I don't want to process the "SelectionChanged" at this moment; only when the user has interacted with the control themselves (eg. if they select "Custom…", I can reveal a separate text-box and give that focus; but I don't want to do that when I'm setting up the page and they had "Custom…" previously selected, as otherwise the user gets the keyboard appearing as soon as they open the Settings page...)
Similarly, I've found that ToggleSwitch controls will fire "Checked" and "Unchecked" events when the "IsChecked" Property is changed to something new. Again, is there a way to ignore or suppress this event when changed by code? (I kind-of got around this for now by just using "Clicked", but from a learning standpoint, it'd be nice to know how to deal with it).
I was thinking maybe there was some way to get the "origin" (eg. "code" or "user input") of the Event from the "SelectionChangedEventArgs" or "RoutedEventArgs"... but maybe not?
I also tried setting an "initialized" bool value ("false" by default, set to "true" after Constructor is run, and wrap the Event-handling code in something like "if (initialized) { ... }"; but the event still seemed to be fired after the Constructor was done for the "lpColour.ItemSource=..." and "lpColour.SelectedIndex = 1" code that was done while "initialized" was "false". Very strange. :P
I hope I'm explaining that clearly - I've never posted here before!
I'd appreciate any help you could offer. Thanks!
UPDATE - thanks to #MyKuLLSKI's answer, that's given me a great place to work from.
As a side note, building on the idea, I tried keeping them as 'List's initially and having the "IgnoreSelectionChanged" as an int that would 'count down' (so before setting the ListPicker's ItemSource, I'd set "IgnoreSelectionChanged+=2" (to account for the two events that would get fired); similarly I'd set "IgnoreSelectionChanged++" just before setting the SelectedIndex manually... that seemed to work too.
However, using the "ObservableCollection" bound to the ListPicker and relying on that to tell of changes seems perhaps a better way than using the ListPicker's own "SelectionChanged" event, so I'll modify my code to use that instead.
Thanks again!
I'll try to answer all your questions/problems
The reason why you are having trouble setting the ItemSource in XAML is because im almost certain you have some Binding issues. For Bindings to work you need to have a DataContext and Binding on a UIElement.
Something that is bought to a property must be a DependencyProperty or INotifyPropertyChanged
Also a List is not a good Collection type to bind a ListPicker to. Instead you would probably want to use as ObservableCollextion() instead. This if this collection is bound to the ListPicker and the items change the ListPicker will be automatically updated.
The reason why the SelectionChanged Event gets fired 2 times is because you are changed it twice. When the ListPicker is first created the Selected item is null or -1 because no items are in it. Then when you set the ItemSource it automatically changed the SelectedIndex to 0 then you change it to 1.
One way is to add a flag every time the user you know your changing the variable in code
Silverlight lacks an IsLoaded Property so you ma want to add a bool when the Page gets loaded to true.
When Binding doesn't change the property in the UIElement. Instead change the property its bound to.
Below is my solution that should solve all your issues (WP7.1):
XAML
<phone:PhoneApplicationPage
x:Class="WP7Sandbox.MainPage"
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"
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True"
Loaded="PhoneApplicationPageLoaded">
<Grid>
<StackPanel>
<toolkit:ListPicker ItemsSource="{Binding ListPickerCollection, Mode=TwoWay}" SelectionChanged="ListPickerSelectionChanged" SelectedIndex="{Binding ListPickerSelectedIndex, Mode=TwoWay}"/>
<Button Click="ButtonClick" Content="Selection Change and Ignore Event"/>
<Button Click="Button2Click" Content="Selection Change and Trigger Event"/>
<toolkit:ToggleSwitch IsChecked="{Binding ToggleSwitchValue, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</phone:PhoneApplicationPage>
Code Behind
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Phone.Controls;
namespace WP7Sandbox
{
public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private bool IsLoaded;
private bool IgnoreSelectionChanged;
public ObservableCollection<string> ListPickerCollection { get; private set; }
private bool _ToggleSwitchValue;
public bool ToggleSwitchValue
{
get
{
return _ToggleSwitchValue;
}
set
{
_ToggleSwitchValue = value;
OnPropertyChanged("ToggleSwitchValue");
}
}
private int _ListPickerSelectedIndex;
public int ListPickerSelectedIndex
{
get
{
return _ListPickerSelectedIndex;
}
set
{
_ListPickerSelectedIndex = value;
OnPropertyChanged("ListPickerSelectedIndex");
}
}
public MainPage()
{
InitializeComponent();
ListPickerCollection = new ObservableCollection<string>()
{
"Red",
"Blue",
"Green",
"Custom…"
};
}
private void PhoneApplicationPageLoaded(object sender, RoutedEventArgs e)
{
IsLoaded = true;
}
private void ListPickerSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (IsLoaded && !IgnoreSelectionChanged)
{
}
IgnoreSelectionChanged = false;
}
private void ButtonClick(object sender, RoutedEventArgs e)
{
// I want to ignore this SelectionChanged Event
IgnoreSelectionChanged = true;
ChangeListPickerSelectedIndex();
}
private void Button2Click(object sender, RoutedEventArgs e)
{
// I want to trigger this SelectionChanged Event
IgnoreSelectionChanged = false; // Not needed just showing you
ChangeListPickerSelectedIndex();
}
private void ChangeListPickerSelectedIndex()
{
if (ListPickerSelectedIndex - 1 < 0)
ListPickerSelectedIndex = ListPickerCollection.Count - 1;
else
ListPickerSelectedIndex--;
}
}
}
A lot is there but it should help