1
votes

I'm struggling with databinding in WPF having searched around I still can't see what I am missing to get the model to update with the selected item from the list. I have 2 entities a Slot and a SlotType

public class Slot : BindableObject, INotifyPropertyChanged
{
    private int slotID;
    public int SlotID
    {
        get { return slotID; }
        set
        {
            if (slotID != value)
            {
                slotID = value;
                RaisePropertyChanged("SlotID");
            }
        }
    }
    private string user;
    public string SlotUser
    {
        get { return user; }
        set
        {
            if (user != value)
            {
                user = value;
                RaisePropertyChanged("SlotUser");
            }
        }
    }

    private int slotUserID;
    public int SlotUserID
    {
        get { return slotUserID; }
        set
        {
            if (slotUserID != value)
            {
                slotUserID = value;
                RaisePropertyChanged("SlotUserID");
            }
        }
    }

    private int slotType;
    public int SlotType
    {
        get { return slotType; }
        set
        {
            if (slotType != value)
            {
                slotType = value;
                RaisePropertyChanged("SlotType");
            }
        }
    }

    private DateTime slotStartDateTime;
    public DateTime SlotStartDateTime
    {
        get { return slotStartDateTime; }
        set
        {
            if (slotStartDateTime != value)
            {
                slotStartDateTime = value;
                RaisePropertyChanged("SlotStartDateTime");
            }
        }
    }
public class SlotType : BindableObject, INotifyPropertyChanged
{
    private int slotTypeID;
    public int SlotTypeID
    {
        get { return slotTypeID; }
        set
        {
            if (slotTypeID != value)
            {
                slotTypeID = value;
                RaisePropertyChanged("SlotTypeID");
            }
        }
    }
    private string slotTypeDesc;
    public string SlotTypeDesc
    {
        get { return slotTypeDesc; }
        set
        {
            if (slotTypeDesc != value)
            {
                slotTypeDesc = value;
                RaisePropertyChanged("SlotTypeDesc");
            }
        }
    }
    public override string ToString()
    {
        return SlotTypeDesc;
    }
}

I then create a class containing both and set my window context to be this class.

public class SlotWithTypes 
{
    public ObservableCollection<SlotType> slotTypes { get; set; }
    public Slot slot { get; set; }
}

From my main window I test and if required open new window displaying a list of SlotTypes for the user to select which type is relevant to the slot being created.

Slot slot = new Slot();
slot.SlotStartDateTime = item.SlotStartDateTime;
if (item.SlotType == 0)
{
    SlotWithTypes swtype = new SlotWithTypes();
    swtype.slotTypes = slotTypes;
    swtype.slot = slot;

    SelectSlotType stype = new SelectSlotType();
    stype.DataContext = swtype;
    stype.ShowDialog();
}

Finally in my XAML I have my listbox

<Grid>
    <ListBox Name="lstSlotTypes" 
             HorizontalAlignment="Left" 
             Height="200" 
             Margin="0,10,0,0" 
             VerticalAlignment="Top" 
             Width="194"                 
             ItemsSource="{Binding slotTypes}" 
             SelectedItem="{Binding Path=slot.Type, Mode=TwoWay}"
             SelectedValue="{Binding slotTypes.SlotTypeID, Mode=TwoWay}" 
             SelectedValuePath="{Binding slot.Type, Mode=TwoWay}" 
             DisplayMemberPath="{Binding slotTypes.SlotTypeDesc}"                  
             SelectionChanged="lstSlotTypes_SelectionChanged">
    </ListBox>
    <TextBox HorizontalAlignment="Left" 
             Height="23" 
             Margin="10,246,0,0" 
             TextWrapping="Wrap" 
             Text="{Binding slot.SlotStartDateTime, Mode=TwoWay}" 
             VerticalAlignment="Top" 
             Width="74"/>
</Grid>

For testing I put in a textbox bound to the slotstartdatetime which works and updates back to my model. I have tried a variety of binding formats on the listbox and can get my list of SlotTypes to display put can't get the Slot entity to update with the selected value.

I realise this has become a long question but please if someone can see what I'm doing wrong?

2
what is the logic? 1. there is a list of items when I open the window? 2. Is there any items presented in listbox?Ilan
A you sure, that in SelectedValuePath and DisplayMemberPath you need Binding? :) There is must be some property name.Spawn

2 Answers

2
votes

There ar a several problems with the code:

  1. There are two bindings errors in visual studio OUTPUT window (in case when the list box source is defined and contains elements), the reason is wrong binding of SelectedValuePath an source type collision. Here are the solution which is worked for me.
  2. Data context collection initialisation:

    private void InitDataContext()
    {
        DataContext = new SlotWithTypes
        {
            slot = new Slot(),
            slotTypes = new ObservableCollection<SlotType>(new List<SlotType>
            {
                new SlotType
                {
                    SlotTypeDesc = "Bla Bla 1",
                    SlotTypeID = 1,
                },
                new SlotType
                {
                    SlotTypeDesc = "Bla Bla 2",
                    SlotTypeID = 2,
                }
            })
        };
    }
    
  3. Xaml code:

    <Grid>
    <ListBox Name="lstSlotTypes" HorizontalAlignment="Left" 
             Height="200" Margin="0,10,0,0" 
             VerticalAlignment="Top" Width="194" 
             ItemsSource="{Binding slotTypes}" 
             SelectedValue="{Binding SelectedSlotType, Mode=TwoWay}" 
             SelectedValuePath="SlotTypeID" DisplayMemberPath="SlotTypeDesc" 
             SelectionChanged="lstSlotTypes_SelectionChanged">
    </ListBox></Grid>
    
  4. SlotWithTypes code:

    public ObservableCollection<SlotType> slotTypes { get; set; }
    
    public Slot slot
    {
        get { return _slot; }
        set
        {
            _slot = value;
            OnPropertyChanged();
        }
    }
    
    public int SelectedSlotType
    {
        get { return _selectedSlotType; }
        set
        {
            _selectedSlotType = value;
            OnPropertyChanged();
            UpdateSlot(SelectedSlotType);
        }
    }
    
    private void UpdateSlot(int selectedSlotType)
    {
        slot.SlotType = selectedSlotType;
    }
    
  5. As a result each time I make a selection SlotType property of the slot will be changed ad defined with the new value. Do your logic here.

regards,

1
votes

I can see many things wrong here, firstly, that's how your xaml should look like to get this work:

    <ListBox Name="lstSlotTypes" HorizontalAlignment="Left" Height="200" Margin="0,10,0,0" VerticalAlignment="Top" Width="194"                 
        ItemsSource="{Binding slotTypes}" 
             SelectedValue="{Binding Path=slot.SlotType}"
             SelectedValuePath="SlotTypeID"
  DisplayMemberPath="SlotTypeDesc" >
    </ListBox>

Now, when you don't know what the bindings are not working, in most cases that is because of binding errors visible in the output window, it can be really helpfull, so look there each time. For example on your xaml, that is what the output window showed:

System.Windows.Data Error: 40 : BindingExpression path error: 'Type' property not found on 'object' ''Slot' (HashCode=62085918)'. BindingExpression:Path=slot.Type; DataItem='SlotWithTypes' (HashCode=23184054); target element is 'ListBox' (Name='lstSlotTypes'); target property is 'SelectedItem' (type 'Object') System.Windows.Data Error: 40 : BindingExpression path error: 'SlotTypeID' property not found on 'object' ''ObservableCollection`1' (HashCode=58050239)'. BindingExpression:Path=slotTypes.SlotTypeID; DataItem='SlotWithTypes' (HashCode=23184054); target element is 'ListBox' (Name='lstSlotTypes'); target property is 'SelectedValue' (type 'Object')

As you can see, there already some information.

Now what is wrong with your xaml:

  • the Slot class doesn't have a Type property, only a SlotType property
  • You can't bind to the slot.SlotType the SelectedItem because the SelectedItem holds the whole selected object from the ItemsSource - in that case the SlotType class, whereas the bound property slot.SlotType is of type int. So you need to use the SelectedValue property, not the SelectedItem in that case
  • It looks like you messed SelectedValue with SelectedValuePath, the SelectedValue binding points to the object in datacontext where the value of the selected item should be stored, in that case = slot.SlotType
  • SelectedValuePath - it's the path on the selected item which should be set as the selected value - it's a static string, no need to bind anything
  • DisplayMemberPath - as above, it's a static string indicating the path to the property holding the value which needs to be displayed - no need to bind anything here