Blend/XAML: bind ItemsSource via button - c#

I am creating a Windows 8.1 app and have the following code:
<ListView x:Name="listView" Height="505" Canvas.Left="35" Canvas.Top="75" Width="300" ItemTemplate="{StaticResource GroupTemplate}" ItemsSource="{Binding Groups, Mode=TwoWay, UpdateSourceTrigger=Explicit}" HorizontalAlignment="Left" VerticalAlignment="Top" UseLayoutRounding="False" ScrollViewer.VerticalScrollBarVisibility="Disabled" SelectionMode="None" />
<Button Height="40" Width="260" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="40" Canvas.Top="585" BorderThickness="0" Content="Overige tonen" Background="#FF9B9B9B" Style="{StaticResource ButtonStyle1}" Click="show_items2" />
When the user clicks the show_items2 button, the ItemsSource Groups should be replaced by another ItemsSource. I use sampledata 'Groups' from Blend for Visual Studio 2013. I have the pretty obvious code for the button, shown below:
private void show_Medicatie2(object sender, RoutedEventArgs e)
{
// replace itemsSource with another
}
I've tried many tutorials but nothing seams to work. Do I miss something? Help is really appreciated. Thank you!

You need to RaisePropertyChanged("Groups") when you change Groups

The class which you are binding to (in your case, the owner of Groups) is required to be an INotifyPropertyChanged so that you can explicitly tell the WPF that the binding needs refreshing. This is a requirement if you are not using DependencyProperties.
Add this to your class definition:
class X : System.ComponentModel.INotifyPropertyChanged
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
public IEnumerable<object> Groups //Replace 'object' with the type your are using...
{
get{ return m_Groups; }
set
{
m_Groups = value;
//Raise the event to notify that the property has actually got a new value
var handler = PropertyChanged;
if(handler != null)
PropertyChanged(this, new PropertyChangedEventArgs("Groups"));
}
} IEnumerable<object> m_Groups = new List<object>();
}
Why does this work?
WPF knows about INotifyPropertyChanged and when you use an object as a Binding source, WPF checks if it implements the interface. If it does, WPF subscribes to the event. When the event is triggered, the property name is checked and if it matches the last property in the binding path, the binding is updated.
You can also replace IEnumerable<T> with ObservableCollection<T> but things start getting tricky soon afterwards. I recommended reading up about ObservableCollection<T>!

Related

UWP x:bind issue - Invalid binding path 'dpl' : Property 'dpl' not found on type 'DataTemplate'

I have a class PricingData and PricingSchedule. Where PricingSchedule is a List<> inside PricingData class. I want to bind data of this class to UWP controls.
Sample code is available to download here : https://github.com/jigneshdesai/SampleOfBindingIssue1.git
How Code looks: i have a start page(mainpage) that hosts ListView control, Listview has PricingUserControl within it. PricingUserControl looks like this
<TextBlock x:Name="lblPriceHeader" Text="{Binding PricingTitle}" Margin="0,0,50,0" />
<ComboBox x:Name="cbPriceValueList" ItemsSource="{x:Bind dpl}" DisplayMemberPath="PriceValue" SelectedValuePath="PriceValue" SelectedValue="{Binding DisplayPricing}" />
<ListView x:Name="lbPriceChangeSchedule" ItemsSource="{Binding PricingScheduleList}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="cbSchedulePriceValueList" ItemsSource="{x:Bind dpl}" DisplayMemberPath="PriceValue" SelectedValuePath="PriceValue" />
<TextBlock Text="{Binding SchedulePricingTimeZone }" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
What i want to achieve: Combobox should populate a list of values (eg. 1USD, 2USD, 3USD etc.). Then when you provide List of records from database, the listbox will repeat PricingUserControl and combobox within it should set its value property (SelectedValue) as per record.
Issue:
ComboBox x:Name="cbPriceValueList" uses x:bind dpl where dpl is a local variable of PricingUserControl. It populates the list properly. The trouble is ComboBox x:Name="cbSchedulePriceValueList" it also has x:bind dpl but during compilation it display error "Invalid binding path 'dpl' : Property 'dpl' not found on type 'DataTemplate'."
I am wondering why x:bind dpl does not work at this point. ?
I have now realized that your problem is in fact that you need to reach to a Page property from within the DataTemplate, so here is a updated answer.
You cannot use x:Bind if you need to access an outside element's property from within a DataTemplate. Instead, you can use classic {Binding} expression. First add a name to your page:
<Page
...
x:Name="Page">
And now refer to this name from within the DataTemplate:
<ComboBox
x:Name="cbSchedulePriceValueList"
ItemsSource="{Binding ElementName=Page, Path=dpl}"
DisplayMemberPath="PriceValue"
SelectedValuePath="PriceValue" />
Original answer
To be able to use x:Bind inside of a DataTemplate, you must specify the data type the individual items of the control will have, using x:DataType. Suppose your PricingScheduleList is a List<MyApp.Models.MyType>, then you will first need to add this XML namespace to the <Page> element:
xmlns:models="using:MyApp.Models"
And then set the x:DataType attribute as follows:
<DataTemplate x:DataType="models:MyType">
...
</DataTemplate>
You can confirm this works by the fact that IntelliSense should now suggest you the properties of MyType when you start writing the x:Bind expression.
By checking your code, the reason why SelectedValue does not take effect is when you choose the item from ComboBox, you didn't notify your DisplayPricing to change. So you need to implement INotifyPropertyChanged interface in your PricingData. Do the same behavior in PricingSchedule.
public class PricingData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
......
public string DisplayPricing
{
get => $"{PricingValue} {PricingCurrency}";
set
{
var sp = value.Split(' ');
PricingValue = sp.First();
PricingCurrency = sp.Last();
OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
PScheduleUserControl.xaml:
<ComboBox x:Name="cbPriceValueList" ItemsSource="{x:Bind myList}" DisplayMemberPath="PriceValue" SelectedValuePath="PriceValue" SelectedValue="{Binding DisplayPricing,Mode=TwoWay}" />

Listen for generic events

I have a function I would like to run on after update of a lot of different text boxes, is it possible to listen for a generic after update event rather than the specific events?
So rather than 100 individual calls to the function, just one listener?
Edit: It would appear we are using a combination of MVVM and traditional code behind.
Here is one of the textboxes:
<TextBox Text="{Binding APhaseFrom}" x:Name="txtFromWhereA" TabIndex="26" HorizontalContentAlignment="Center" HorizontalAlignment="Left" Height="48" TextWrapping="NoWrap" VerticalAlignment="Top" Width="261" FontSize="26" FontWeight="Bold" BorderBrush="Black" BorderThickness="1" Margin="289,656,0,0" GotMouseCapture="txtFromWhereA_GotMouseCapture" GotFocus="txtFromWhereA_GotFocus" Grid.Row="3" />
The code from the view Model:
public string APhaseFrom
{
get { return new string((char[])_f.Rows[1].GetValue("Alpha09")); }
set
{
if (value.Length <= 35)
{
_f.Rows[1].SetValue("Alpha09", value);
}
else
{
MessageBox.Show("Error: String length Longer than 35 Characters.");
}
}
}
We also are using some commands for other processes:
public ICommand Updatesql
{
get;
internal set;
}
private void CreateUpdatesql()
{
Updatesql = new RelayCommand(UpdatesqlExecute);
}
private void UpdatesqlExecute()
{
_f.Update();
}
Should I be using commands or just link the events to functions in the viewmodel?
Since you are using WPF, and if I understand your problem correctly, then the RoutedEvents that WPF uses may help you here. Essentially, events like the LostFocus event of a TextBox will bubble up your UI hierarchy and can be handled by a common parent control. Consider this snippet of XAML and codebehind:
<StackPanel TextBox.LostFocus="TextBoxLostFocus">
<TextBox></TextBox>
<TextBox></TextBox>
<TextBox></TextBox>
</StackPanel>
Codebehind:
private void TextBoxLostFocus(object sender, RoutedEventArgs e)
{
MessageBox.Show("Lost Focus!");
}
You will find that the event handler is called for any of the three textboxes when focus is lost. The sender parameter or e.Source can be used to find the textbox that fired the event.
This pattern holds true for any RoutedEvent, so things like Button.Click or TextBox.TextChanged and many more can be caught in this manner.
Really and truthfully you should be using a single design pattern... ie MVVM when writing WPF applications, each textbox would be bound to a property which implements the INotifyPropertyChange interface.
In the setter of each property you would essentially update the value, fire a property changed event and then either make a call to your method or simply add an event handler on the view model for the PropertyChanged event.
Also... MessageBox.Show is a bad idea in your view models, its hard to unit test it.
Update
I removed my previous ideas because I now understand more clearly what you are looking for.
But you definitely need to use the LostFocus event.
<TextBox Text="{Binding APhaseFrom}" x:Name="txtFromWhereA" LostFocus="OnLostFocus" />

INotifyPropertyChanged not working with WPF data binding

I have a combobox in my MainWindow.xaml file like so:
<ComboBox Name="material1ComboBox"
HorizontalAlignment="Center"
MinWidth="100"
ItemsSource="{Binding ViewProperties.MaterialDropDownValues}"
SelectedValue="{Binding ViewProperties.Material1SelectedValue}"
SelectionChanged="Material1ComboBoxSelectionChanged">
</ComboBox>
I've assigned the datacontext in the codebehind using this.datacontext = this.
I created a ViewProperties that is accessed as a property in the MainWindow and is a class that implements INotifyPropertyChanged and contains the MaterialDropDownValues as a property.
I even changed the the MaterialDropDownValues to be an ObservableCollection.
The problem is that the databinding works on initialisation however if the MaterialDropDownValues property is changed the combobox values are not updated.
I have the following in the ViewProperties class:
public ObservableCollection<string> MaterialDropDownValues
{
get { return this.materialDropDownValues; }
set
{
this.materialDropDownValues = value;
OnPropertyChanged("MaterialDropDownValues");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
Any ideas why this is not working? All the other answers I could find advised to implement INotifyPropertyChanged and make the property an observablecollection.
Solution 1:
Dont recreate this.materialDropDownValues try to do
this.materialDropDownValues.Clear();
foreach(var mystring in myStrings)
this.materialDropDownValues.Add(mystring);
for all new items. If this doesnt work then try solution 2...
Solution 2:
As per my experience, I think ObservableCollection of primitive types like int, string, bool, double etc. does not refresh on Property Change notification if ItemsControl.ItemTemplate is not specified.
<ComboBox Name="material1ComboBox"
HorizontalAlignment="Center"
MinWidth="100"
ItemsSource="{Binding ViewProperties.MaterialDropDownValues}"
SelectedValue="{Binding ViewProperties.Material1SelectedValue}"
SelectionChanged="Material1ComboBoxSelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type System:String}">
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This is because the itemscontrol's items container creates non-observable item containers in it for primitive data by simply copying item.ToString(). In the code above the {Binding} should update the data changes when the whole items source is changed.
Let me know if this works.
When I bump into things like this, the first thing I do is play around with the binding mode. Something like:
ItemsSource="{Binding Path=ViewProperties.MaterialDropDownValues, Mode=TwoWay}"
That sometimes trips you up. The other thing I would make sure of is that if you're instantiating new ViewProperties object(s) following your initial load, you notify change on that. If you don't, the XAML will be referring to an outdated version of the object while your code behind/view model is operating on a different one.
Edit in response to comments
None of the below solved the problem, but is left as a reference.
Original Answer
The problem is that you have not specified the DataContext for your view, which is where WPF looks for Binding values by default.
Provided that your ViewProperties property on MainWindow is public you can simply change your binding to:
ItemsSource="{Binding ViewProperties.MaterialDropDownValues,
RelativeSource={RelativeSource FindAncestor, AncestorType=Window}"
This causes WPF to look for the property value on the first occurence of Window that it finds above the combobox in the visual tree.
Alternatively, you can just set the Window.DataContext property to your instance of ViewProperties and change the binding to the following:
ItemsSource="{Binding MaterialDropDownValues}"
Either of these will work, but I would suggest using the latter as it is closer to the MVVM pattern that is generally preferred in WPF/XAML applications.
what happens if you change your xaml to
<ComboBox Name="material1ComboBox"
HorizontalAlignment="Center"
MinWidth="100"
DataContext="{Binding ViewProperties}"
ItemsSource="{Binding MaterialDropDownValues}"
SelectedValue="{Binding Material1SelectedValue}"
SelectionChanged="Material1ComboBoxSelectionChanged">
</ComboBox>
nevertheless you should just instantiate your collection once and just use remove, add and clear when you use a OberservableCollection.
Posting this in case anyone else runs into this. I came up this as the best search result matching my symptoms, but it turns our that none of the answers worked above for me.
I was using WinUI3 and apparently it uses the newer x:Bind syntax for it's XAML. Apparently x:Bind defaults it's Mode to OneTime which is why it wouldn't update after the first value (I also tried Binding but couldn't get that to work)
From: <TextBlock Text="{x:Bind MyField}" x:Phase="1" Margin="0,5,0,5"/>
To: <TextBlock Text="{x:Bind MyField, Mode=OneWay}" x:Phase="1" Margin="0,5,0,5"/>
So if you are using x:Bind, make sure set Mode=OneWay AND implement INotifyPropertyChanged and then things should work

WPF Binding Via StaticResouces

Given the following Xaml:
<Window.Resources>
<System:String x:Key="StringValue"></System:String>
</Window.Resources>
<Grid>
<ComboBox Margin="137,101,169,183" ItemsSource="{Binding collection}" SnapsToDevicePixels="True" IsHitTestVisible="true">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Command="{Binding CheckCommand}" IsChecked="{Binding IsChecked}" Content="{Binding Name}"/>
<TextBlock Text="{StaticResource StringValue}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
What I want is for the Textblock Text to be bound to a static resource, that is databound to a value on the ViewModel. The issue is System.String appears to not allow databinding. ANybody know of a way to do this? For context, the TextBlock needs a different itemssource than that of its parent combobox.
Thanks.
String doesnt allow binding because it is not a DependencyObject (and doesnt implement INotifyPropertyChanged)
but why dont you just bind directly to the Value in the ViewModel?
if you cannot bind to a ViewModel (think about RelativeSource with searching Parent type) you can implement a wrapper (which implements INotifyPropertyChanged to get the changes in the object)
Example wrapper class:
public class BindWrapper<T> : INotifyPropertyChanged
{
private T _Content;
public T Content
{
get
{
return _Content;
}
set
{
_Content = value;
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
how to instantiate and bind in XAML:
<Window.Resources>
<local:BindWrapper x:Key="wrapper" x:TypeArguments="System:String">
<local:BindWrapper.Content>
<System:String>huuu</System:String>
</local:BindWrapper.Content>
</local:BindWrapper>
</Window.Resources>
<TextBlock Text="{Binding Source={StaticResource wrapper}, Path=Content}" />
To clarify, A System.String has no dependency properties so you can't bind it anything. I think you need a convertor so your TextBlock can bind to the View Model. What type of ObservableCollection do you have on the View Model?
EDIT If you just want to bind a simple string to the text property this is the wrong answer. If you want to bind to formatted text, read on.
I was having this problem before. I wanted to bind my TextBlock to a string resource in my properties. I ended up subclassing TextBlock to BindableTextBlock and making and a Convertor for string to an Inline list.
Question and Answers here.
It may seem a little involved, there ought to be an easier way. However I've resused the control several times whenever I've needed to bind to some formatted text and it works. Hopefully you can benefit from my work, and perhaps improve.

Two-way binding to a dataset - updating the database?

Okay, so I'm fairly new to WPF and data binding, but I've searched and searched and can't seem to find an answer to this.
I have a database (called Inventory), and a dataset (called DevicesDataSet). What I'm trying to do is bind the dataset to a listbox, so that a specific device from the Devices table of the DevicesDataSet can be selected, and have its properties displayed in a TextBox for editing.
The following is the XAML I have so far:
<Window x:Class="Inventory.SignOutDevice"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SignOutDevice" Height="339" Width="392" xmlns:my="clr-namespace:Inventory" Loaded="Window_Loaded">
<Window.Resources>
<my:DevicesDataSet x:Key="devicesDataSet" />
<CollectionViewSource x:Key="devicesViewSource" Source="{Binding Path=Devices, Source={StaticResource devicesDataSet}}" />
</Window.Resources>
<Grid DataContext="{StaticResource devicesViewSource}">
<ListBox Height="237" HorizontalAlignment="Left" Margin="12,12,0,0" Name="listBox1"
VerticalAlignment="Top" Width="254" ItemsSource="{Binding}" SelectedValuePath="Selected">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Make}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Margin="223,267,0,0" Text="{Binding Path=Make, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
Whenever a device is selected and a property is edited (I'm only displaying one property at the moment), the listbox is updated, but the dataset (database?) doesn't seem to be. That is, when I leave the form and then come back to it, the listbox returns to its original state.
So I guess the question is: how do I make these changes persist/write to the database?
Edit: Derp, here's the updated backend C#:
using System.Windows;
using System.Data;
namespace Inventory
{
public partial class SignOutDevice : Window
{
DevicesDataSet devicesDataSet = null;
Inventory.DevicesDataSetTableAdapters.DevicesTableAdapter devicesDataSetDevicesTableAdapter = null;
public SignOutDevice()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
devicesDataSet = ((Inventory.DevicesDataSet)(this.FindResource("devicesDataSet")));
devicesDataSetDevicesTableAdapter = new Inventory.DevicesDataSetTableAdapters.DevicesTableAdapter();
devicesDataSetDevicesTableAdapter.Fill(devicesDataSet.Devices);
System.Windows.Data.CollectionViewSource devicesViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("devicesViewSource")));
devicesViewSource.View.MoveCurrentToFirst();
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
devicesDataSet.AcceptChanges();
devicesDataSetDevicesTableAdapter.Update(devicesDataSet.Tables["Devices"].Select(null, null, DataViewRowState.Deleted));
devicesDataSetDevicesTableAdapter.Update(devicesDataSet.Tables["Devices"].Select(null, null, DataViewRowState.ModifiedCurrent));
devicesDataSetDevicesTableAdapter.Update(devicesDataSet.Tables["Devices"].Select(null, null, DataViewRowState.Added));
}
}
}
Have you looked in the Output window to check for binding errors? Because it looks to me like you have one. The TextBox is bound to the Make property, but its DataContext is the devicesViewSource resource. There's nothing associating it with the selected item in the ListBox.
A way to associate the two would be to assign the ListBox a name, and then set the binding on the TextBox to {Binding ElementName=MyListBox, Path=Make, Mode=TwoWay}.
Okay, figured it out. Turns out the auto-generated Update() method in devicesDataSetDevicesTableAdapter didn't actually do anything. Made a custom method with the Query Wizard and now all is well.

Categories

Resources