Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien

[C#] ListView - 2 datatypen - itemDataBound

Pagina: 1
Acties:

  • PdeBie
  • Registratie: Juni 2004
  • Laatst online: 16:21
Hoi allen,

ik zit even met een klein vraagstukje hoe ik iets het beste kan aanpakken.

Ik heb 2 typen (Article en SubArticle) en ik heb een listview.
Deze listview wordt gevuld met Articles OF met Subarticles. De typename en dataObjectTypeName van de datasource wordt dynamisch bepaald aan de hand van een parameter. Dus als de parameter waarde x heeft, wordt de typename 'Article' en anders 'SubArticle'.

Nu loop ik alleen in het ItemDataBound event tegen een probleem aan:

In het event vul ik de labels (die in de ItemTemplates staan) van de listview, met enkele properties van het dataItem. Bijvoorbeeld:
C#:
1
2
lblActive.Text = string.Format("{0}", 
    ((Article)e.Item.DataItem).Active ? "Ja" : "Nee");


Probleem is echter, dat dit fout gaat als de items van het type 'SubArticle' zijn. Dan zou ik e.Item.DataItem als 'SubArticle' moeten casten. Alleen dan krijg ik dus allemaal dubbele code.

C#:
1
2
3
4
5
6
//if is article
lblActive.Text = string.Format("{0}", 
    ((Article)e.Item.DataItem).Active ? "Ja" : "Nee");
//else if is subarticle
lblActive.Text = string.Format("{0}", 
    ((SubArticle)e.Item.DataItem).Active ? "Ja" : "Nee");


Hoe kan ik dit het handigste refactoren?

--edit--
Om het overal te moeten casten tegen te gaan, heb ik eerst een variabele 'article' gedeclareerd die ik vul met het dataItem. Deze regel roep ik aan het begin van het event aan, maar hiermee blijft het probleem bestaan natuurlijk.

C#:
1
2
3
4
var article = (Article)e.Item.DataItem;

lblActive.Text = string.Format("{0}", 
    article.Active ? "Ja" : "Nee");

[ Voor 13% gewijzigd door PdeBie op 04-06-2013 11:26 ]


  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Ik inherit gewoon altijd van ListViewItem; dan krijg je dus (voor een Foo en Bar class) een FooListViewItem en een BarListViewItem. Die XXX-listviewitem-objecten zoeken zelf maar uit hoe ze hun labels invullen of andere properties gebruiken. Vervolgens maak je gewoon een static factorymethod (of extension method of whatever) die een juiste instantie van die listviewitems returned en die listviewitems mik je vervolgens in je listview.

Geef ze vervolgens een gemeenschappelijke (al-dan-niet abstract) base-class en/of interface et voila.


C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
using System;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            listView1.Items.AddRange(new ListViewItem[] { 
                new FooListviewItem(new Foo() { FooVal = "foo1", FooBarVal = "fb1"}), 
                new BarListviewItem(new Bar() { BarVal = "bar1", FooBarVal = "fb2"}), 
                new FooListviewItem(new Foo() { FooVal = "foo2", FooBarVal = "fb3"}), 
                new BarListviewItem(new Bar() { BarVal = "bar2", FooBarVal = "fb4"}), 
            });
        }
    }

    public interface IFooBar
    {
        string FooBarVal { get; }
    }

    public abstract class Foobar : IFooBar
    {
        public string FooBarVal { get; set; }
    }

    public class Foo : Foobar
    {
        public string FooVal { get; set; }
    }

    public class Bar : Foobar
    {
        public string BarVal { get; set; }
    }

    public abstract class FooBarListviewItem : ListViewItem
    {
        public IFooBar Item { get; private set; }

        public FooBarListviewItem(IFooBar foobar)
            : base(foobar.FooBarVal) {
                this.Item = foobar;
        }
    }

    public class FooListviewItem : FooBarListviewItem
    {
        public FooListviewItem(Foo foo)
            : base(foo)
        {
            this.SubItems.Add(foo.FooVal);
        }
    }

    public class BarListviewItem : FooBarListviewItem
    {
        public BarListviewItem(Bar bar)
            : base(bar)
        {
            this.SubItems.Add(bar.BarVal);
        }
    }
}

Of zo:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    public interface IFooBarListviewItem<T>
    {
        T Item { get; }
    }

    public class FooListviewItem : ListViewItem, IFooBarListviewItem<IFooBar>
    {
        public IFooBar Item { get; private set; }

        public FooListviewItem(Foo foo)
            : base(new[] { foo.FooBarVal, foo.FooVal })
        {
            this.Item = foo;
        }
    }

    public class BarListviewItem : ListViewItem, IFooBarListviewItem<IFooBar>
    {
        public IFooBar Item { get; private set; }

        public BarListviewItem(Bar bar)
            : base(new[] { bar.FooBarVal, bar.BarVal })
        {
            this.Item = bar;
        }
    }

[ Voor 102% gewijzigd door RobIII op 04-06-2013 12:05 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


  • kutagh
  • Registratie: Augustus 2009
  • Laatst online: 22-11 20:04
Het eenvoudigst? Gebruik een gemeenschappelijke interface en cast naar de interface in plaats van de daadwerkelijke type.

  • PdeBie
  • Registratie: Juni 2004
  • Laatst online: 16:21
RobIII schreef op dinsdag 04 juni 2013 @ 11:38:
Ik inherit gewoon altijd van ListViewItem; dan krijg je dus (voor een Foo en Bar class) een FooListViewItem en een BarListViewItem. Die XXX-listviewitem-objecten zoeken zelf maar uit hoe ze hun labels invullen of andere properties gebruiken. Vervolgens maak je gewoon een static factorymethod (of extension method of whatever) die een juiste instantie van die listviewitems returned en die listviewitems mik je vervolgens in je listview.
Gaat even puzzelen worden (nog nooit gedaan op deze manier), maar moet lukken denk ik :)
Bedankt alvast.

  • PdeBie
  • Registratie: Juni 2004
  • Laatst online: 16:21
Blijkt toch iets spannender dan ik dacht haha. :)
Weet je toevallig een goede tutorial over interfaces?

Heb deze gevonden http://www.codeproject.co...rfaces-in-C-For-Beginners, maar die vind ik wat summier en zit vol met kleine foutjes.

[ Voor 6% gewijzigd door PdeBie op 04-06-2013 14:56 ]


  • Cyphax
  • Registratie: November 2000
  • Laatst online: 23:00

Cyphax

Moderator LNX
Ik denk dat het probleem niet zozeer is dat dat artikel summier is maar dat je er meer achter zoekt dan erachter zit. :P
Een interface is heel simpel; zet alle functies die ze gemeenschappelijk hebben zonder functie-inhoud daarin en bij het definiëren van je class zet je achter de dubbele punt de naam van je interface. Vervolgens moet je de functies die je in je interface hebt staan implementeren. Zo'n interface is wat dat betreft meer een soort afspraak die je vastlegt.

Saved by the buoyancy of citrus


  • ThomasG
  • Registratie: Juni 2006
  • Laatst online: 18:54
Ik weet niet waar je precies naar opzoek bent qua tutorial. Simpel gezegd is een interface een hele abstracte klasse, welke niks anders doet dan specificeren welke methoden (in bepaalde talen ook welke eigenschappen) de overervende klasse moeten hebben. Je kunt het alleen kun je het niet initialiseren, en geen functionaliteit aan geven. Je kunt het wel als variable type gebruiken, naar casten, etc.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface IAnimal {
    public string Action();
}
class Cat : IAnimal {
    public string Action() {
        return "Mew";
    }
}
class Dog : IAnimal {
    public string Action() {
        return "Bark!";
    }
}

IAnimal cat = new Cat();
cat.Action(); // Mew

Dog dog = new Dog();
dog.Action(); // Bark

cat = (IAnimal) dog;
cat.Action(); // Bark

  • PdeBie
  • Registratie: Juni 2004
  • Laatst online: 16:21
ok, er begint wat te dagen. Maar snap het principe nog niet helemaal.

In mijn situatie heb ik 2 typen. Article en SubArticle met beiden dezelfde properties welke in de listview moeten komen. Ik kan op basis van die properties een klasse maken, bv. ListViewArticle, met daarin alle velden die ik wil toewijzen aan de elementen binnen de listview item templates (bv. labels voor strings, checkboxen voor booleans)

Daarnaast kan ik een interface maken, bijvoorbeeld IListViewItem. ListViewArticle overerft hiervan. Dus:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
interface IListViewItem
{
    int Id { get; }
    string Description { get; }
    bool Active { get; }
}

class ListViewArticle : IListViewItem
{
    public int Id { get; set; }
    public string Description { get; set; }
    public bool Active { get; set; }
}

class Article : ListViewArticle
{
    public Article()
    {
    }
}

class SubArticle : ListViewArticle
{
    public SubArticle()
    {
    }
}


Vervolgens kan ik beiden typen aan de listview koppelen, want ik kan de gebinde items casten naar 'ListViewArticle' en op basis van die properties de velden in de listview vullen. (Let op: De listviews roepen dezelfde OnItemDataBound event handler aan)

HTML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<asp:ListView ID="ListView1" runat="server" OnItemDataBound="ListView_OnItemDataBound">
        <ItemTemplate>
            <p>
                <asp:Label runat="server" ID="Label1"></asp:Label><br />
                <asp:Label runat="server" ID="Label2"></asp:Label><br />
                <asp:CheckBox runat="server" ID="CheckBox1" /><br />
                <asp:CheckBox runat="server" ID="CheckBox2" />
            </p>
        </ItemTemplate>
    </asp:ListView>
    
    <asp:ListView ID="ListView2" runat="server" OnItemDataBound="ListView_OnItemDataBound">
        <ItemTemplate>
            <p>
                <asp:Label runat="server" ID="Label1"></asp:Label><br />
                <asp:Label runat="server" ID="Label2"></asp:Label><br />
                <asp:CheckBox runat="server" ID="CheckBox1" /><br />
                <asp:CheckBox runat="server" ID="CheckBox2" />
            </p>
        </ItemTemplate>
    </asp:ListView>


C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public partial class WebForm4 : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            var articles = new List<Article>
                    {
                        new Article {Id = 1, Description = "Peugeot", Active = true},
                        new Article {Id = 2, Description = "Renault", Active = true},
                        new Article {Id = 3, Description = "Citroen", Active = false}
                    };

            ListView1.DataSource = articles;
            ListView1.DataBind();


            var subArticles = new List<SubArticle>
                    {
                        new SubArticle {Id = 1, Description = "Velgen", Active = false},
                        new SubArticle {Id = 2, Description = "Claxon", Active = false},
                        new SubArticle {Id = 3, Description = "Ruitenwisser", Active = false}
                    };

            ListView2.DataSource = subArticles;
            ListView2.DataBind();
            
        }

        protected void ListView_OnItemDataBound(object sender, ListViewItemEventArgs e)
        {
            var listViewArticle = (ListViewArticle)e.Item.DataItem;

            var labelId = (Label)e.Item.FindControl("Label1");
            labelId.Text = listViewArticle.Id.ToString(CultureInfo.InvariantCulture);

            var labelDescription = (Label)e.Item.FindControl("Label2");
            labelDescription.Text = listViewArticle.Description;

            var checkboxActive = (CheckBox)e.Item.FindControl("CheckBox1");
            checkboxActive.Checked = listViewArticle.Active;
        }
    }


Dit heb ik in een test projectje gedaan en ik krijg inderdaad 2 listviews met dezelfde velden, met het verschil dat ik aan de ene listview Articles koppel en aan de andere SubArticles.

Ik zie in dit verhaal alleen de meerwaarde van de interface nog niet. Want als ik deze weg laat en ListViewArticle hier niet van laat overerven, werkt het ook.
Dus waarom dan een interface maken?

  • ThomasG
  • Registratie: Juni 2006
  • Laatst online: 18:54
In dit geval heb je inderdaad geen interface nodig, omdat je ListViewArticle in dit geval die taak overneemt. Of je had de ListViewArticle klasse weg kunnen laten, en alleen de interface gebruiken. Omdat ListViewArticle zelf geen functionaliteit implementeert. Maar dan zou je de eigenschappen in beide klassen moeten definiëren. Maar in tegen stelling tot klassen, kun je meerdere interfaces overerven. Als je bijvoorbeeld naar System.Collections.Hashtable kijkt, zie je dat deze meerdere interfaces overerft.

Zo heeft hij de interface IDictionary, wat er voor zorgt dat Hashtable een methode Add, Clear, etc moet hebben. Zo kun je een Hashtable meegeven aan een methode welke een IDictionary als parameter accepteert. SortedList bijv. overerft ook IDictionary, waardoor je deze ook aan die methode zou kunnen voeren. Die methode hoeft dan niet te weten wat er intern gebeurd, maar omdat hij weet dat het object een Add methode heeft, kan hij die gebruiken.

[ Voor 11% gewijzigd door ThomasG op 04-06-2013 16:29 ]


  • PdeBie
  • Registratie: Juni 2004
  • Laatst online: 16:21
Als ik lees 'interface', versta ik dat als 'koppeling' (letterlijk vertaald).
Maar eigenlijk is het geen koppeling, maar meer een blanco klasse definitie. Am I correct?

Snap ik alleen het verschil tussen een interface en een abstracte klasse nog niet, maar daarvoor ben ik dit artikel aan het proberen te begrijpen:
http://stackoverflow.com/...erface-and-abstract-class

[ Voor 43% gewijzigd door PdeBie op 04-06-2013 16:36 ]


  • ThomasG
  • Registratie: Juni 2006
  • Laatst online: 18:54
Ja. Een stukje uit de .NET handleiding.
An interface contains only the signatures of methods, properties, events or indexers. A class or struct that implements the interface must implement the members of the interface that are specified in the interface definition.
Mogelijk heb je hier iets aan, Interfaces (C# Programming Guide.)
pdebie schreef op dinsdag 04 juni 2013 @ 16:34:
Snap ik alleen het verschil tussen een interface en een abstracte klasse nog niet, maar daarvoor ben ik dit artikel aan het proberen te begrijpen:
http://stackoverflow.com/...erface-and-abstract-class
Een abstracte klassen is een soort tussenvariant. Je kunt het vergelijken met een interface, maar hier kun je wel functionaliteit in implementeren. Het enigste wat je niet kun doen, is het initialiseren. Maar deze geld qua overerving als een normale klasse, en kun je dus (in C#) maar een keer doen.
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
abstract class Abstract {
    void Foo();
    void Bar() {
        DoSomethingFunny();
    }
}

class Dummy : Abstract {
    void Foo() {
        DoSomethingElse();
    }
}

Abstract foo = new Abstract(); // Error
Abstract bar = new Dummy();

bar.Foo(); // DoSomethingElse
bar.Bar(); // DoSomethingFunny

[ Voor 55% gewijzigd door ThomasG op 04-06-2013 16:45 ]


  • PdeBie
  • Registratie: Juni 2004
  • Laatst online: 16:21
Ja, zo had ik het ook begrepen in het StackOverflow voorbeeld.

Bedankt! :)

Vanavond/Morgen eens verder spelen met deze informatie. Kijken of ik het nu kan toepassen in mijn echte listview in plaats van het test project.

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
Niet direct relevant voor het probleem waar hier hulp voor gezocht wordt, maar data bind operaties op child controls kun je beter organiseren binnen de DataBind method van je Page of Control, i.p.v. ze met de hand te binden in Page_Load.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public partial class WebForm4 : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // Perform one and only one initial data binding. The ASP.NET
        // WebForms infrastructure will automatically bind data on all
        // nested child controls, recursively.
        DataBind();
    }

    public override void DataBind()
    {
        // Pass data sources to nested controls in preparation of the
        // nested DataBind call that will happens as part of the ASP.NET
        // WebForms infrastructure.
        ListView1.DataSource = new List<Article>
        {
            new Article {Id = 1, Description = "Peugeot", Active = true},
            new Article {Id = 2, Description = "Renault", Active = true},
            new Article {Id = 3, Description = "Citroen", Active = false}
        };

        ListView2.DataSource = new List<SubArticle>
        {
            new SubArticle {Id = 1, Description = "Velgen", Active = false},
            new SubArticle {Id = 2, Description = "Claxon", Active = false},
            new SubArticle {Id = 3, Description = "Ruitenwisser", Active = false}
        };
        
        // Call the base DataBind operation to have `<%# %>` data binding
        // expressions in the .ascx/.aspx template be populated and to have
        // nested calls of the DataBind method on child controls happen.
        base.DataBind();
    }

    protected void ListView_OnItemDataBound(object sender, ListViewItemEventArgs e)
    {
        var listViewArticle = (ListViewArticle)e.Item.DataItem;

        var labelId = (Label)e.Item.FindControl("Label1");
        labelId.Text = listViewArticle.Id.ToString(CultureInfo.InvariantCulture);

        var labelDescription = (Label)e.Item.FindControl("Label2");
        labelDescription.Text = listViewArticle.Description;

        var checkboxActive = (CheckBox)e.Item.FindControl("CheckBox1");
        checkboxActive.Checked = listViewArticle.Active;
    }
}


Als je je eigen maakt om data binding op deze manier af te handelen, dan bespaar je jezelf op de lange termijn een hoop ongemak met delen van de ASP.NET WebForms page/control lifecycle die elkaar in de weg zouden gaan zitten.

  • PdeBie
  • Registratie: Juni 2004
  • Laatst online: 16:21
Ik heb tot op heden eigenlijk nog nooit problemen hiermee gehad.
Wat voor problemen zou ik dan tegenaan kunnen lopen (mocht ik er toch ineens tegenaan lopen)?

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
pdebie schreef op dinsdag 04 juni 2013 @ 22:53:
Ik heb tot op heden eigenlijk nog nooit problemen hiermee gehad.
Wat voor problemen zou ik dan tegenaan kunnen lopen (mocht ik er toch ineens tegenaan lopen)?
Binds die verkeerd om gaan lopen, bijv. vóór een (bijgewerkte) DataSource gezet is. Dat zijn hele lastige zaken om later nog te herstellen, kan ik je verklappen. (Zelf gedwongen met dat bijltje te hakken tijdens een afgelopen upgrade v/e legacy codebase.)

In het algemeen natuurlijk ook dubbele binds die nodeloos extra resources (RAM, CPU tijd, etc.) verbruiken.

  • PdeBie
  • Registratie: Juni 2004
  • Laatst online: 16:21
ok, ik zal het in mijn testproject eens implementeren. Kijken wat het precies doet.

  • PdeBie
  • Registratie: Juni 2004
  • Laatst online: 16:21
even vervolg vraagje.

Ik heb de databind override geimplementeerd zoals jij aangeeft, maar:
Bovenaan mijn listview heb ik een aantal zoekvelden en 2 knoppen, te weten 'zoeken' en 'herstel overzicht'.

Op het moment dat ik op herstel overzicht klik, moet de listview weer het geheel tonen. Ik zal dus in de Page_Load om de DataBind() call een check moeten doen of het een postback is of niet.
Anders voert hij de databind 2x uit. Eenmaal in de load en eenmaal in het click event van de knop.

Klopt dit? Of hoe zou jij dit aanpakken?

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
Dat klopt. En dat is ook gelijk waarom WebForms niet meer van deze tijd is en je beter met ASP.NET MVC aan de gang kunt.

Het is gewoon heel moeilijk om de page lifecycle in bedwang te houden als je voor het verwerken van interactie met push buttons in user controls vanwege het idiote 'alles in één <form> element'-principe vast zit aan __doPostBack en event handlers. Je wordt min of meer gedwongen om het foute pad op te gaan.

WebForms is fijn voor mensen die van WinForms afkomen en niet gewend zijn voor het web te programmeren (precies de doelgroep waarvoor WebForms origineel ontwikkeld werd), maar de wijze waarop de architecturele insteek van WinForms naar het web overgebracht is is absoluut ongeschikt om een moderne, performante web applicatie in te schrijven.


Wat ik zelfs zou doen als ik niet onder WebForms uit zou kunnen komen, is overal ViewState uitschakelen, bovenin de pagina de <form runat="server"> meteen sluiten en verder niets van die troep gebruiken. Daarna op de server met de (in ASP.NET 4.5 nieuwe) model binding attributen werken zoals FormAttribute om parameters uit posts vanuit je eigen <form> tags te plukken.

[ Voor 23% gewijzigd door R4gnax op 05-06-2013 18:54 ]


  • PdeBie
  • Registratie: Juni 2004
  • Laatst online: 16:21
OK, dan zal ik het voor nu op deze manier moeten doen. Het project is te groot om alles op z'n kop te gooien hiervoor.

Maar ik ben inmiddels bezig met het verdiepen in MVC. Moet alleen de hele gedachten gang erachter nog zien te begrijpen.
Pagina: 1