Delen via


Een aangepaste sorteringsgebruikersinterface (VB) maken

door Scott Mitchell

PDF downloaden

Wanneer u een lange lijst met gesorteerde gegevens weergeeft, kan het handig zijn om gerelateerde gegevens te groeperen door scheidingstekens te introduceren. In deze zelfstudie ziet u hoe u een dergelijke gebruikersinterface voor sorteren maakt.

Introductie

Bij het weergeven van een lange lijst met gesorteerde gegevens waarbij er slechts een handvol verschillende waarden in de gesorteerde kolom staan, kan een eindgebruiker het moeilijk vinden om te bepalen waar, precies, de verschilgrenzen voorkomen. Er zijn bijvoorbeeld 81 producten in de database, maar slechts negen verschillende categoriekeuzen (acht unieke categorieën plus de NULL optie). Overweeg het geval van een gebruiker die geïnteresseerd is in het onderzoeken van de producten die onder de categorie Zeevruchten vallen. Op een pagina met alle producten in één GridView kan de gebruiker besluiten om de resultaten te sorteren op categorie, die alle visproducten samen zal groeperen. Na het sorteren op categorie moet de gebruiker vervolgens door de lijst zoeken, op zoek naar waar de producten met visgroepen beginnen en eindigen. Omdat de resultaten alfabetisch zijn gesorteerd op de categorienaam, is het niet moeilijk om de zeevruchtenproducten te vinden, maar het vereist nog steeds het nauwkeurig doorzoeken van de lijst met items in het raster.

Om de grenzen tussen gesorteerde groepen te benadrukken, maken veel websites gebruik van een gebruikersinterface waarmee een scheidingsteken tussen dergelijke groepen wordt toegevoegd. Met scheidingstekens zoals in afbeelding 1 kan een gebruiker sneller een bepaalde groep vinden en de grenzen identificeren, en bepalen welke afzonderlijke groepen in de gegevens bestaan.

Elke categoriegroep wordt duidelijk geïdentificeerd

Afbeelding 1: Elke categoriegroep is duidelijk geïdentificeerd (klik om de afbeelding op volledige grootte weer te geven)

In deze zelfstudie ziet u hoe u een dergelijke gebruikersinterface voor sorteren maakt.

Stap 1: Een standaard, sorteerbare rasterweergave maken

Voordat we de GridView uitbreiden om de verbeterde sorteerinterface te bieden, gaan we eerst een standaard, sorteerbare GridView maken waarin de producten worden vermeld. Open eerst de CustomSortingUI.aspx pagina in de PagingAndSorting map. Voeg een GridView toe aan de pagina, stel de ID eigenschap in op ProductList, en koppel deze aan een nieuwe ObjectDataSource. Configureer de ObjectDataSource om de ProductsBLL klasse GetProducts() methode te gebruiken voor het selecteren van records.

Configureer vervolgens de GridView zodanig dat deze alleen de ProductName, CategoryName, en SupplierNameUnitPrice BoundFields en het Stopgezette SelectievakjeVeld bevat. Ten slotte configureert u de GridView om sorteren te ondersteunen door het selectievakje 'Sorteren inschakelen' aan te vinken in de smarttag van de GridView (of door de eigenschap ervan AllowSorting in te true stellen). Nadat u deze toevoegingen aan de CustomSortingUI.aspx pagina hebt gemaakt, moet de declaratieve markering er ongeveer als volgt uitzien:

<asp:GridView ID="ProductList" runat="server" AllowSorting="True"
    AutoGenerateColumns="False" DataKeyNames="ProductID"
    DataSourceID="ObjectDataSource1" EnableViewState="False">
    <Columns>
        <asp:BoundField DataField="ProductName" HeaderText="Product"
            SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category"
            ReadOnly="True" SortExpression="CategoryName" />
        <asp:BoundField DataField="SupplierName" HeaderText="Supplier"
            ReadOnly="True" SortExpression="SupplierName" />
        <asp:BoundField DataField="UnitPrice" DataFormatString="{0:C}"
            HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice" />
        <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
            SortExpression="Discontinued" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts"
    TypeName="ProductsBLL"></asp:ObjectDataSource>

Neem even de tijd om onze voortgang tot nu toe in een browser te bekijken. In afbeelding 2 ziet u de sorteerbare GridView wanneer de gegevens op categorie in alfabetische volgorde worden gesorteerd.

De sorteerbare GridView-gegevens zijn gesorteerd op categorie

Afbeelding 2: De sorteerbare rasterweergavegegevens zijn gesorteerd op categorie (klik om de afbeelding op volledige grootte weer te geven)

Stap 2: Technieken voor het toevoegen van de scheidingstekenrijen verkennen

Nu de algemene, sorteerbare GridView is voltooid, hoeft u alleen de scheidingstekenrijen in de GridView toe te voegen vóór elke unieke gesorteerde groep. Maar hoe kunnen dergelijke rijen worden geïnjecteerd in de GridView? In wezen moeten we de rijen van GridView doorlopen, bepalen waar de verschillen optreden tussen de waarden in de gesorteerde kolom en vervolgens de juiste scheidingstekenrij toevoegen. Wanneer u nadenkt over dit probleem, lijkt het natuurlijk dat de oplossing zich ergens in de gebeurtenis-handler van RowDataBound GridView bevindt. Zoals we hebben besproken in de zelfstudie Aangepaste opmaak op basis van gegevens , wordt deze gebeurtenis-handler vaak gebruikt bij het toepassen van opmaak op rijniveau op basis van de gegevens van de rij. De RowDataBound gebeurtenis-handler is hier echter niet de oplossing, omdat rijen niet programmatisch vanuit deze gebeurtenis-handler aan GridView kunnen worden toegevoegd. De GridView-collectie is eigenlijk alleen-lezen.

Als u extra rijen wilt toevoegen aan de GridView, hebt u drie opties:

  • Voeg deze rijen voor metagegevensscheidingstekens toe aan de werkelijke gegevens die zijn gebonden aan de GridView
  • Nadat de GridView is gebonden aan de gegevens, voegt u extra TableRow exemplaren toe aan de verzameling besturingselementen van GridView
  • Een aangepast serverbesturingselement maken dat het GridView-besturingselement uitbreidt en deze methoden overschrijft die verantwoordelijk zijn voor het maken van de structuur van GridView

Het maken van een aangepast serverbeheer is de beste benadering als deze functionaliteit nodig was op veel webpagina's of op verschillende websites. Het zou echter nogal wat code met zich meebrengen en een grondige verkenning van de diepten van de interne werking van GridView. Daarom wordt deze optie voor deze zelfstudie niet overwogen.

De andere twee opties waarmee scheidingstekenrijen worden toegevoegd aan de werkelijke gegevens die zijn gebonden aan de GridView en het bewerken van de verzameling besturingselementen van GridView nadat deze gebonden zijn, vallen het probleem anders aan en verdienen een discussie.

Rijen toevoegen aan de gegevens die zijn gebonden aan de GridView

Wanneer GridView is gebonden aan een gegevensbron, wordt er een GridViewRow gemaakt voor elke record die door de gegevensbron wordt geretourneerd. Daarom kunnen we de benodigde scheidingstekenrijen injecteren door scheidingstekenrecords toe te voegen aan de gegevensbron voordat deze aan de GridView worden gekoppeld. In afbeelding 3 ziet u dit concept.

Eén techniek omvat het toevoegen van scheidingsrijen aan de gegevensbron

Afbeelding 3: Eén techniek omvat het toevoegen van scheidingsrijen aan de gegevensbron

Ik gebruik de term 'scheidingsrecords' tussen aanhalingstekens omdat er geen speciaal scheidingsrecord is; in plaats daarvan moeten we op een of andere manier aangeven dat een bepaald record in de gegevensbron fungeert als scheiding in plaats van een normale gegevensrij. Voor onze voorbeelden koppelen we een ProductsDataTable instantie aan de GridView, die bestaat uit ProductRows. We kunnen een record markeren als een scheidingstekenrij door de eigenschap CategoryID op -1 te zetten (aangezien een dergelijke waarde normaal niet zou kunnen bestaan).

Als u deze techniek wilt gebruiken, moet u de volgende stappen uitvoeren:

  1. De gegevens programmatisch ophalen om verbinding te maken met gridview (een ProductsDataTable exemplaar)
  2. De gegevens sorteren op basis van de GridView-s SortExpression en SortDirection -eigenschappen
  3. Zoek door de ProductsRows naar verschillen in de gesorteerde ProductsDataTable kolom, en zoek waar deze verschillen zich bevinden.
  4. Injecteer bij elke groepsgrenswaarde een exemplaar van een scheidingstekenrecord ProductsRow in de gegevenstabel, een exemplaar waarop CategoryID is ingesteld op -1 (of op welke aanduiding is besloten om een scheidingstekenrecord te markeren)
  5. Nadat u de scheidingstekenrijen hebt geïnjecteerd, verbindt u de gegevens programmatisch met gridView

Naast deze vijf stappen moeten we ook een gebeurtenis-handler opgeven voor de GridView-gebeurtenis RowDataBound . Hier controleren we elk DataRow en bepalen we of het een scheidingsrij was, waarbij de instelling van CategoryID-1 was. Zo ja, dan willen we waarschijnlijk de opmaak of de tekst aanpassen die in de cel(en) wordt weergegeven.

Als u deze techniek gebruikt voor het injecteren van de sorteergroepgrenzen, is iets meer werk vereist dan hierboven is beschreven, omdat u ook een gebeurtenis-handler moet opgeven voor de gridView-gebeurtenis Sorting en de SortExpression waarden SortDirection moet bijhouden.

De verzameling besturingselementen van GridView bewerken nadat het gegevensgebonden is

In plaats van de gegevens te verzenden voordat deze aan GridView worden gekoppeld, kunnen we de scheidingstekenrijen toevoegen nadat de gegevens zijn gebonden aan de GridView. Het proces van gegevensbinding bouwt de besturingshiërarchie van GridView op, wat in werkelijkheid gewoon een Table exemplaar is dat bestaat uit een verzameling rijen, die elk bestaat uit een verzameling cellen. Met name bevat de verzameling besturingselementen van GridView een Table object in de hoofddirectory, een GridViewRow (die is afgeleid van de TableRow klasse) voor iedere record in de DataSource gebonden aan de GridView en een TableCell object in elke GridViewRow instantie voor elk gegevensveld in het DataSource.

Als u scheidingstekenrijen tussen elke sorteergroep wilt toevoegen, kunnen we deze besturingshiërarchie rechtstreeks bewerken zodra deze is gemaakt. We kunnen er zeker van zijn dat de besturingshiërarchie van GridView voor de laatste keer is gemaakt op het moment dat de pagina wordt weergegeven. Daarom overschrijft deze methode de Page klasse s Render methode, waarbij de uiteindelijke controlehiërarchie van de GridView wordt bijgewerkt om de benodigde scheidingsrijen op te nemen. In afbeelding 4 ziet u dit proces.

Een alternatieve techniek bewerkt de besturingshiërarchie van GridView

Afbeelding 4: Met een alternatieve techniek wordt de besturingshiërarchie van GridView bewerkt (klik om de afbeelding op volledige grootte weer te geven)

Voor deze zelfstudie gebruiken we deze laatste benadering om de gebruikerservaring voor sorteren aan te passen.

Opmerking

De code die ik in deze handleiding laat zien, is gebaseerd op het voorbeeld in het blogbericht van Teemu Keiski, Een beetje spelen met GridView Sort Grouping.

Stap 3: De scheidingstekenrijen toevoegen aan de hiërarchie van rasterweergave

Omdat we de scheidingstekenrijen pas willen toevoegen aan de besturingshiërarchie van GridView nadat deze is aangemaakt en voor de laatste keer tijdens dat paginabezoek is aangemaakt, willen we deze toevoeging uitvoeren aan het einde van de levenscyclus van de pagina, maar voordat de daadwerkelijke GridView-besturingshiërarchie in HTML is weergegeven. Het laatste mogelijke punt waarop we dit kunnen doen, is de Page klasse s Render gebeurtenis, die we in onze code-behind-klasse kunnen overschrijven met behulp van de volgende methode-handtekening.

Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
   ' Add code to manipulate the GridView control hierarchy
   MyBase.Render(writer)
End Sub

Wanneer de oorspronkelijke Page methode van de Render klasse wordt aangeroepenbase.Render(writer), worden alle besturingselementen op de pagina weergegeven, waardoor de markeringen worden gegenereerd op basis van hun besturingshiërarchie. Daarom is het noodzakelijk dat we beide aanroepen base.Render(writer), zodat de pagina wordt weergegeven en dat we de besturingshiërarchie van GridView bewerken vóór het aanroepen base.Render(writer), zodat de scheidingstekenrijen zijn toegevoegd aan de besturingshiërarchie van GridView voordat deze wordt weergegeven.

Als u de kopteksten van de sorteergroep wilt injecteren, moet u eerst controleren of de gebruiker heeft gevraagd of de gegevens moeten worden gesorteerd. De inhoud van GridView is standaard niet gesorteerd en daarom hoeven we geen groepssorteringskoppen in te voeren.

Opmerking

Als u wilt dat de GridView wordt gesorteerd op een bepaalde kolom wanneer de pagina voor het eerst wordt geladen, roept u de methode GridView Sort aan op het eerste paginabezoek (maar niet op volgende postbacks). Doe dit door deze aanroep toe te voegen aan de Page_Load gebeurtenishandler binnen een if (!Page.IsPostBack) voorwaardelijke structuur. Raadpleeg de handleiding Over paging- en sorteerrapportgegevens voor aanvullende informatie over de Sort methode.

Ervan uitgaande dat de gegevens zijn gesorteerd, is onze volgende taak om te bepalen op welke kolom de gegevens zijn gesorteerd en vervolgens de rijen te scannen die zoeken naar verschillen in de waarden van die kolom. De volgende code zorgt ervoor dat de gegevens zijn gesorteerd en de kolom worden gevonden waarop de gegevens zijn gesorteerd:

Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
    ' Only add the sorting UI if the GridView is sorted
    If Not String.IsNullOrEmpty(ProductList.SortExpression) Then
        ' Determine the index and HeaderText of the column that
        'the data is sorted by
        Dim sortColumnIndex As Integer = -1
        Dim sortColumnHeaderText As String = String.Empty
        For i As Integer = 0 To ProductList.Columns.Count - 1
            If ProductList.Columns(i).SortExpression.CompareTo( _
                ProductList.SortExpression) = 0 Then
                sortColumnIndex = i
                sortColumnHeaderText = ProductList.Columns(i).HeaderText
                Exit For
            End If
        Next
        ' TODO: Scan the rows for differences in the sorted column�s values
End Sub

Als de GridView nog moet worden gesorteerd, is de eigenschap GridView SortExpression niet ingesteld. Daarom willen we alleen de scheidingstekenrijen toevoegen als deze eigenschap een bepaalde waarde heeft. Als dit het geval is, moeten we vervolgens de index van de kolom bepalen waarop de gegevens zijn gesorteerd. Dit wordt bereikt door de Columns verzameling van GridView te doorlopen en te zoeken naar de kolom waarvan SortExpression de eigenschap gelijk is aan de eigenschap van GridView SortExpression. Naast de kolomindex nemen we ook de HeaderText eigenschap mee, die wordt gebruikt bij het weergeven van de scheidingsrijen.

Met de index van de kolom waarop de gegevens worden gesorteerd, is de laatste stap het inventariseren van de rijen van de GridView. Voor elke rij moeten we bepalen of de waarde van de gesorteerde kolom verschilt van de waarde van de vorige rij gesorteerde kolom. Als dat het geval is, moeten we een nieuw GridViewRow exemplaar in de besturingshiërarchie injecteren. Dit wordt bereikt met de volgende code:

Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
    ' Only add the sorting UI if the GridView is sorted
    If Not String.IsNullOrEmpty(ProductList.SortExpression) Then
        ' ... Code for finding the sorted column index removed for brevity ...
        ' Reference the Table the GridView has been rendered into
        Dim gridTable As Table = CType(ProductList.Controls(0), Table)
        ' Enumerate each TableRow, adding a sorting UI header if
        ' the sorted value has changed
        Dim lastValue As String = String.Empty
        For Each gvr As GridViewRow In ProductList.Rows
            Dim currentValue As String = gvr.Cells(sortColumnIndex).Text
            If lastValue.CompareTo(currentValue) <> 0 Then
                ' there's been a change in value in the sorted column
                Dim rowIndex As Integer = gridTable.Rows.GetRowIndex(gvr)
                ' Add a new sort header row
                Dim sortRow As New GridViewRow(rowIndex, rowIndex, _
                    DataControlRowType.DataRow, DataControlRowState.Normal)
                Dim sortCell As New TableCell()
                sortCell.ColumnSpan = ProductList.Columns.Count
                sortCell.Text = String.Format("{0}: {1}", _
                    sortColumnHeaderText, currentValue)
                sortCell.CssClass = "SortHeaderRowStyle"
                ' Add sortCell to sortRow, and sortRow to gridTable
                sortRow.Cells.Add(sortCell)
                gridTable.Controls.AddAt(rowIndex, sortRow)
                ' Update lastValue
                lastValue = currentValue
            End If
        Next
    End If
    MyBase.Render(writer)
End Sub

Deze code begint met het programmatisch verwijzen naar het Table object dat is gevonden in de hoofdmap van de besturingshiërarchie van GridView en het maken van een tekenreeksvariabele met de naam lastValue. lastValue wordt gebruikt om de gesorteerde kolomwaarde van de huidige rij te vergelijken met de vorige rijwaarde. Vervolgens wordt de verzameling GridView Rows geïnventariseerd en voor elke rij wordt de waarde van de gesorteerde kolom opgeslagen in de currentValue variabele.

Opmerking

Als u de waarde van de gesorteerde kolom van de bepaalde rij wilt bepalen, gebruikt u de eigenschap cel Text . Dit werkt goed voor BoundFields, maar werkt niet naar wens voor TemplateFields, CheckBoxFields, enzovoort. We bekijken hoe u binnenkort rekening moet houden met alternatieve GridView-velden.

De currentValue variabelen en lastValue variabelen worden vervolgens vergeleken. Als ze verschillen, moeten we een nieuwe scheidingstekenrij toevoegen aan de besturingshiërarchie. Dit wordt bereikt door de index van GridViewRow in de Table objectverzameling Rows te bepalen, nieuwe GridViewRow en TableCell exemplaren te maken, en vervolgens de TableCell en GridViewRow aan de besturingshiërarchie toe te voegen.

Houd er rekening mee dat de scheidingstekenrij TableCell is opgemaakt, zodat deze de volledige breedte van de GridView beslaat, is opgemaakt met behulp van de SortHeaderRowStyle CSS-klasse en de eigenschap heeft Text , zodat zowel de sorteergroepnaam (zoals Categorie) als de groepswaarde (zoals Drank) wordt weergegeven. Ten slotte wordt lastValue bijgewerkt naar de waarde van currentValue.

De CSS-klasse die wordt gebruikt om de veldnamenrij SortHeaderRowStyle van de sorteergroep op te maken, moet worden opgegeven in het Styles.css bestand. U kunt gerust de stijlinstellingen gebruiken die u aanspreekt; Ik heb het volgende gebruikt:

.SortHeaderRowStyle
{
    background-color: #c00;
    text-align: left;
    font-weight: bold;
    color: White;
}

Met de huidige code voegt de sorteerinterface sorteergroepkoppen toe bij het sorteren op een BoundField (zie afbeelding 5, waarin een schermopname wordt weergegeven bij het sorteren op leverancier). Wanneer u echter sorteert op een ander veldtype (zoals een CheckBoxField of TemplateField), zijn de kopteksten van de sorteergroep nergens te vinden (zie afbeelding 6).

De sorteerinterface bevat groepskoppen wanneer er gesorteerd wordt op BoundFields

Afbeelding 5: De sorteerinterface bevat groepsheaders wanneer er wordt gesorteerd op BoundFields (klik om de afbeelding op volledige grootte weer te geven)

De koppen van de sorteergroep ontbreken bij het sorteren van een CheckBoxField

Afbeelding 6: De kopteksten van de sorteergroep ontbreken bij het sorteren van een selectievakjeveld (klik om de afbeelding op volledige grootte weer te geven)

De reden waarom de sorteergroepkoppen ontbreken bij het sorteren op een CheckBoxField, is omdat de code momenteel alleen de TableCell eigenschap s Text gebruikt om de waarde van de gesorteerde kolom voor elke rij te bepalen. Voor CheckBoxFields is de eigenschap TableCell s Text een lege tekenreeks; in plaats daarvan is de waarde beschikbaar via een Selectievakje-webbesturingselement dat zich binnen de verzameling TableCell bevindt Controls.

Als u andere veldtypen dan BoundFields wilt verwerken, moet u de code uitbreiden waaraan de currentValue variabele is toegewezen om te controleren of er een Selectievakje in de TableCellControls verzameling bestaat. In plaats van currentValue = gvr.Cells(sortColumnIndex).Text, vervang deze code door het volgende:

Dim currentValue As String = String.Empty
If gvr.Cells(sortColumnIndex).Controls.Count > 0 Then
    If TypeOf gvr.Cells(sortColumnIndex).Controls(0) Is CheckBox Then
        If CType(gvr.Cells(sortColumnIndex).Controls(0), CheckBox).Checked Then
            currentValue = "Yes"
        Else
            currentValue = "No"
        End If
        ' ... Add other checks here if using columns with other
        '      Web controls in them (Calendars, DropDownLists, etc.) ...
    End If
Else
    currentValue = gvr.Cells(sortColumnIndex).Text
End If

Met deze code wordt de gesorteerde kolom TableCell voor de huidige rij onderzocht om te bepalen of er besturingselementen in de Controls verzameling zijn. Als er een selectievakje is en het eerste besturingselement een selectievakje is, is de currentValue variabele ingesteld op Ja of Nee, afhankelijk van de eigenschap Selectievakje.Checked Anders wordt de waarde uit de TableCell eigenschap s Text genomen. Deze logica kan worden gerepliceerd voor het afhandelen van sortering voor sjabloonvelden die mogelijk aanwezig zijn in de GridView.

Met de bovenstaande code-toevoeging zijn de kopteksten van de sorteergroep nu aanwezig bij het sorteren op het stopgezette selectievakjeveld (zie afbeelding 7).

De kopteksten van de sorteergroep zijn nu aanwezig bij het sorteren van een selectievakjeveld

Afbeelding 7: De kopteksten van de sorteergroep zijn nu aanwezig bij het sorteren van een selectievakjeveld (klik hier om de volledige afbeelding weer te geven)

Opmerking

Als u producten met NULL databasewaarden voor de CategoryID, SupplierIDof UnitPrice velden hebt, worden deze waarden standaard weergegeven als lege tekenreeksen in GridView, wat betekent dat de tekst van de scheidingstekenrij voor die producten met NULL waarden als Categorie wordt gelezen: (dat wil zeggen, er is geen naam achter Categorie: zoals bij Categorie: Drank). Als u een waarde wilt weergeven, kunt u de eigenschap BoundFields NullDisplayText instellen op de tekst die u wilt weergeven, of u kunt een voorwaardelijke instructie toevoegen in de Render-methode wanneer u de eigenschap van de currentValue scheidingsrij Text toewijst.

Samenvatting

De GridView bevat niet veel ingebouwde opties voor het aanpassen van de sorteerinterface. Met een beetje code op laag niveau is het echter mogelijk om de besturingshiërarchie van GridView aan te passen om een meer aangepaste interface te maken. In deze zelfstudie hebben we gezien hoe u een sorteergroepscheidingstekenrij toevoegt voor een sorteerbare GridView, waarmee de afzonderlijke groepen en die groepsgrenzen gemakkelijker kunnen worden geïdentificeerd. Bekijk het blogbericht Scott Guthrie s A Few ASP.NET 2.0 GridView Sort Tips and Tricks voor aanvullende voorbeelden van aangepaste sorteerinterfaces.

Veel plezier met programmeren!

Over de auteur

Scott Mitchell, auteur van zeven ASP/ASP.NET-boeken en oprichter van 4GuysFromRolla.com, werkt sinds 1998 met Microsoft-webtechnologieën. Scott werkt als onafhankelijk consultant, trainer en schrijver. Zijn laatste boek is Sams Teach Yourself ASP.NET 2.0 in 24 uur. Hij kan worden bereikt op mitchell@4GuysFromRolla.com.