WPF selected value in ComboBox

2019-09-13 15:27发布

I'am struggling with ComboBox in my DataGrid View.

I have 2 Observable Collection. One for Data Grid where column DDV presents selected item of Combobox and second where are all options for CombBox.

Observable Collection DDV_Data (all ComboBox options) is in Observable Collection of ArtikliStoritveData.

My WPF looks like this:

        <DataGrid ItemsSource="{Binding Path=ArtikliStoritveData}" AutoGenerateColumns="False" SelectionMode="Single" CanUserAddRows="True" x:Name="dgArtikliStoritve" HorizontalAlignment="Left" Margin="31,58,0,0" VerticalAlignment="Top" Height="229" Width="612">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Šifra" Binding="{Binding Sifra}" />
            <DataGridTextColumn Header="Naziv" Binding="{Binding Naziv}" Width="200"/>
            <DataGridTextColumn Header="Znesek" Binding="{local:CultureAwareBinding Path=Znesek, StringFormat={}{0:C}}"/>
            <DataGridTextColumn Header="DDV" Binding="{local:CultureAwareBinding Path=DDV}" />
            <DataGridTextColumn Header="EM" Binding="{Binding EM}" />
            <DataGridTextColumn Header="Datum spremembe" Binding="{local:CultureAwareBinding Path=DatumSpremembe}" />
            <DataGridTemplateColumn Header="DDV">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ComboBox
                            x:Name="cmbDDV"
                            ItemsSource="{Binding DDV_Data}"
                            SelectedValuePath="DDV"
                            DisplayMemberPath="DDV"
                            SelectedValue="{Binding DDV1}"
                            IsSynchronizedWithCurrentItem="True"
                            Width="50"
                        />
                        </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
            <DataGridTextColumn Binding="{Binding Artikel_ID}" Width="0" Visibility="Hidden"/>
            <DataGridTextColumn Binding="{Binding SkupinaArtikla}" Width="0" Visibility="Hidden"/>
        </DataGrid.Columns>
    </DataGrid>

And my result in DataGrid is:

enter image description here

It is obvious that selected item is not working. What I'am doing wrong?

I am also wondering why ComboBox in new row doesn't get bind?

DDV_Data is part of Observable Collection which is binded to DataGrid:

                ArtikliStoritveData.Add(new ArtikliStoritve
                {    
                    Artikel_ID = Convert.ToInt32(dt.Rows[i]["artikel_id"].ToString()),
                    SkupinaArtikla = Convert.ToInt32(dt.Rows[i]["skupina_artikla"].ToString()),
                    Sifra = dt.Rows[i]["sifra"].ToString(),
                    EM = dt.Rows[i]["em"].ToString(),
                    Naziv = dt.Rows[i]["naziv"].ToString(),
                    DDV = Convert.ToDecimal(dt.Rows[i]["ddv"].ToString()),
                    DDV_Data = DDV_Data1,
                    SelectedItem = "22.0",
                    Znesek = Decimal.Parse(dt.Rows[i]["znesek"].ToString()) ,
                    DatumSpremembe = DateTime.Parse(dt.Rows[i]["date_changed"].ToString())
                });    

DDV Property in ArtikliStoritve model:

    public decimal DDV
    {
        get { return _ddv; }
        set { _ddv = value; }

I have also noticed that when I change value in ComboBox ith changes in every row???

ArtikliStoritve:

class ArtikliStoritve
{

    #region private varaibles
    int _artikel_id;
    int _skupinaArtikla;
    string _sifra;
    string _naziv;
    string _EM;
    decimal _ddv;
    decimal _znesek;
    DateTime _datum_spremembe;

    #endregion

    #region properties
    public int Artikel_ID
    {
        get { return _artikel_id; }
        set { _artikel_id = value; }
    }

    public int SkupinaArtikla
    {
        get { return _skupinaArtikla; }
        set { _skupinaArtikla = value; }
    }

    public string Sifra
    {
        get { return _sifra; }
        set { _sifra = value; }
    }

    public string EM
    {
        get { return _EM; }
        set { _EM = value; }
    }

    public string Naziv
    {
        get { return _naziv; }
        set { _naziv = value; }
    }

    public decimal DDV1
    {
        get { return _ddv; }
        set { _ddv = value; }
    }

    public decimal Znesek
    {
        get { return _znesek;}
        set { _znesek = value; }
    }

    public DateTime DatumSpremembe
    {
        get { return _datum_spremembe; }
        set { _datum_spremembe = value; }
    }

    private decimal _SelectedItem;
    public decimal SelectedItem
    {
        get { return _SelectedItem; }
        set { _SelectedItem = value; }
    }

    private ObservableCollection<DDV_Class> _DDV_Data = new ObservableCollection<DDV_Class>();
    public ObservableCollection<DDV_Class> DDV_Data
    {
        get { return _DDV_Data; }
        set { _DDV_Data = value; }
    }
    #endregion
}

For ComboBox I have a class:

class DDV_Class
{
    private int _ID;
    public int ID
    {
        get { return _ID; }
        set { _ID = value; }
    }

    private decimal _DDV;
    public decimal DDV
    {
        get { return _DDV; }
        set { _DDV = value; }
    }
}

which i Fill in ArtikliStoritveViewModel:

for (int i = 0; i < dtDDV.Rows.Count; i++)
{
    DDV_Data1.Add(new DDV_Class
    {
        ID = Convert.ToInt32(dtDDV.Rows[i]["ID"].ToString()),
        DDV = Convert.ToDecimal(dtDDV.Rows[i]["DDV"].ToString())
    });
}

--> UPDATE

What I did. In ArtikliStoritve:

private DDV_Class _SelectedItem;
public DDV_Class SelectedItem
{
    get { return _SelectedItem; }
    set { _SelectedItem = value; }
}

When filling:

for (int i = 0; i < dt.Rows.Count; ++i)
{
    ArtikliStoritveData.Add(new ArtikliStoritve
    {
        Artikel_ID = Convert.ToInt32(dt.Rows[i]["artikel_id"].ToString()),
        SkupinaArtikla = Convert.ToInt32(dt.Rows[i]["skupina_artikla"].ToString()),
        Sifra = dt.Rows[i]["sifra"].ToString(),
        EM = dt.Rows[i]["em"].ToString(),
        Naziv = dt.Rows[i]["naziv"].ToString(),
        DDV1 = Convert.ToDecimal(dt.Rows[i]["ddv"].ToString()),
        DDV_Data = DDV_Data1,
        SelectedItem = new DDV_Class { ID = 1, DDV = 22.0m },
        Znesek = Decimal.Parse(dt.Rows[i]["znesek"].ToString()),
        DatumSpremembe = DateTime.Parse(dt.Rows[i]["date_changed"].ToString())
    });        
}

In ArtikliStoritveModelView I also have property:

public DDV_Class SelectedItem
{
    get { return ArtikliStoritve.SelectedItem; }
    set { ArtikliStoritve.SelectedItem = value; OnPropertyChanged("SelectedItem"); }
}

WPF look like this:

<DataTemplate>
    <ComboBox
        x:Name="cmbDDV"
        ItemsSource="{Binding DDV_Data}"
        DisplayMemberPath="DDV"
        SelectedItem="{Binding Path=SelectedItem}"
        IsSynchronizedWithCurrentItem="True"
        Width="50"
    />
</DataTemplate>

Result is same like picture above is showing.

--> UPDATE I figure it out why value in all rows changed when I change value in comboBox in one row. Problem vas beacuse I added in each row one istance of Observable Collection:

DDV_Data1 is not instantiate for each row, so this is a problem - one object in all rows:

DataTable dtDDV = myDDV_DAL.getAll();
if (dtDDV.Rows.Count > 0)
{
    for (int i = 0; i < dtDDV.Rows.Count; i++)
    {
        DDV_Data1.Add(new DDV_Class
        {
            ID = Convert.ToInt32(dtDDV.Rows[i]["ID"].ToString()),
            DDV = Convert.ToDecimal(dtDDV.Rows[i]["DDV"].ToString())
        });
    }
}

ArtikliStoritveDAL myArtikliStoritveDAL  = new ArtikliStoritveDAL();
DataTable dt = myArtikliStoritveDAL.getAll();
if (dt.Rows.Count > 0)
{
    for (int i = 0; i < dt.Rows.Count; ++i)
    {
        ArtikliStoritveData.Add(new ArtikliStoritve
        {
            ...
            DDV_Data = DDV_Data1,
            ...

I did my testing on another column where this is now working:

EM_DAL myEM_DAL = new EM_DAL();
DataTable dtEM = myEM_DAL.getAll();
if (dtEM.Rows.Count > 0)
{
    for (int i = 0; i < dtEM.Rows.Count; i++)
    {
        EM_Data.Add(new EM_Model
        {
            ID = dtEM.Rows[i]["EM"].ToString(),
            Naziv = dtEM.Rows[i]["EM"].ToString()
        });
    }
}

    ArtikliStoritveDAL myArtikliStoritveDAL  = new ArtikliStoritveDAL();
DataTable dt = myArtikliStoritveDAL.getAll();
if (dt.Rows.Count > 0)
{
    for (int i = 0; i < dt.Rows.Count; ++i)
    {
        ArtikliStoritveData.Add(new ArtikliStoritve
        {
            ...
            EM_Data = getAll(dt.Rows[i]["em"].ToString()),
            ...


public List<EM_Model> getAll(string p_selected)
{
    List<EM_Model> myEM_Model = new List<EM_Model>();
    string strConnString = Util.getConnectionString();
    try
    {
        NpgsqlConnection conn = new NpgsqlConnection(strConnString);
        DataTable dt = new DataTable();
        conn.Open();
        NpgsqlDataAdapter da = new NpgsqlDataAdapter("SELECT em, em "
                                                        + " FROM em", conn);
        da.Fill(dt);
        conn.Close();
        for (int i = 0; i < dt.Rows.Count; i++)
        {
            myEM_Model.Add(new EM_Model
            {
                ID = dt.Rows[i]["EM"].ToString(),
                Naziv = dt.Rows[i]["EM"].ToString(),
                SelectedItem1 = p_selected
            });
        }
        return myEM_Model;

Now I must figure it out why is value is not getting selected in comboBox. I tested with selected value created in object where all options for comboBox is (getAll()) or in collection ArtikliStoritveData. Neither one is working.

Keep searching for right solution... :)

If I serach contect in one row, Snoop showing me this(which is right):

enter image description here

If do this in WPF, selected value in Combobox is first value in list, not the correct one:

<ComboBox
    x:Name="cmbEM"
    ItemsSource="{Binding EM_Data}"
    DisplayMemberPath="Naziv"
    SelectedItem="{Binding EM}"
    IsSynchronizedWithCurrentItem="True"
    Width="50"
/>

And finally I found a solution. Conjuction of SelectedValue and SelectedValuePath did the trick.

<ComboBox
    x:Name="cmbDDV"
    ItemsSource="{Binding DDV_Data}"
    DisplayMemberPath="DDV"
    SelectedValue="{Binding DDV, Mode=TwoWay}" 
    SelectedValuePath="DDV" 
    IsSynchronizedWithCurrentItem="True"
    Width="50"
/>

On the link I found additional informations which helped me.

Regards, Igor

2条回答
Summer. ? 凉城
2楼-- · 2019-09-13 15:47

I think I can see where your error is... when data binding to a ComboBox.SelectedItem property, there are a few things to note. The first is that the object data bound to the SelectedItem property must be of the same type as the items in the collection that is data bound to the ItemsSource property.

From your code, it seems as though the collection you data bound to the ItemsSource property was of the type of a custom class... you didn't show that, but I guessed because you set the DisplayMemberPath to a value of Naziv. So either you'd need to make your DDV property that you data bind to the SelectedItem property the same type as the items in the collection , or you could try using the ComboBox.SelectedValue property in conjunction with the SelectedValuePath property instead:

<ComboBox x:Name="cmbDDV" 
    ItemsSource="{Binding Path=DDV_Data}"
    DisplayMemberPath="Naziv"
    SelectedValuePath="Naziv"
    IsSynchronizedWithCurrentItem="True"
    SelectedValue="{Binding Path=DDV}"
    Width="50" />

UPDATE >>>

Your latest edit is not what I suggested. All the same, now that you've added the relevant code, I can see that ArtikliStoritveData is a collection of type ArtikliStoritve and DDV_Data is a property in that class which is a collection of type DDV_Class. Therefore, you need a property also of type DDV_Class in your ArtikliStoritve class that you can bind to the SelectedIten property:

<ComboBox x:Name="cmbDDV" 
    ItemsSource="{Binding Path=DDV_Data}"
    SelectedItem="{Binding Path=SelectedItem}"
    IsSynchronizedWithCurrentItem="True"
    Width="50" />

...

private DDV_Class _SelectedItem;
public DDV_Class SelectedItem
{
    get { return _SelectedItem; }
    set { _SelectedItem = value; }
}

private ObservableCollection<DDV_Class> _DDV_Data = new ObservableCollection<DDV_Class>();
public ObservableCollection<DDV_Class> DDV_Data
{
    get { return _DDV_Data; }
    set { _DDV_Data = value; }
}

Just a couple of things to note here for the future... if you want to set the ComboBox.SelectedItem from the code, the item that you set as the value must be an actual item from the collection that is bound to the ComboBox.ItemsSource property. You can do it like this:

SelectedItem = DDV_Data.Where(d => d.ID == someIdValue).Single();

Also, you should have got some errors displayed in the Output Window in Visual Studio... something like:

System.Windows.Data Error: 40 : BindingExpression path error: 'Some' property not found on 'object' ''NameOfDataBoundObject' (Name='')'. BindingExpression:Path=SomePath; DataItem='NameOfDataBoundObject' (Name=''); target element is 'TypeOfUiElement' (Name='NameOfUiElement'); target property is 'PropertyName' (type 'TypeOfProperty')

These are all valuable clues... pay attention to them, because they help you track down your problems.

查看更多
太酷不给撩
3楼-- · 2019-09-13 15:51

I added solution at the end of the question.

Regards, Igor

查看更多
登录 后发表回答