Laden...

Forenbeiträge von OXO Ingesamt 86 Beiträge

16.02.2023 - 11:23 Uhr

Was ich tatsächlich beim INotifyDataErrorInfo auch gut finde, ich die Validierung im ViewModel.
Mein Hauptproblem ist hier tatsächlich die Rückmeldung und Update der UI. Im App-Umfeld gibt es ja meist etwas mit einem Ausrufezeichen neben einem Entry oder einer roten Umrandung des Entry oder rote Schriftart. Zusätzlich müsste das Ganze ja dann auch wieder verschwinden, wenn man die Eingabe gemäß der Validierungs-Logik dann ja wieder richtig gemacht hat. Ein Save-Button darf dann während es noch Fehler gibt, auch erst einmal nix machen.

Aktuell gelingt mir insbesondere das Update der UI und Fehler-Rückmeldung an den User noch nicht. Es wirkt so auf mich, als ob das nicht das bevorzugte Konzept bei .NET MAUI ist (ohne das aber zu wissen). Zusätzlich benutze ich ja auch das Community Toolkit und es wirkt so, als ob in Summe dann mehrere Konzepte vermischt werden. Das fände ich dann nicht so gut

Der funktionierende Link von oben wäre dann der hier:
https://learn.microsoft.com/en-us/dotnet/architecture/maui/validation

Genau, dieses TextValidationBehavior von Dir ist auch noch so ein Weg, der für mich ein eigenes und anderes Konzept darstellt

16.02.2023 - 08:06 Uhr

Jetzt bin ich irgendwie etwas erschlagen von verschiedenen Möglichkeiten der Validierung.
Diese Variante hier wurde mir auch noch angezeigt https://learn.microsoft.com/en-us/dotnet/architecture/maui/validation (Api-Docs). Hier erscheint mir auf den ersten Blick die direkte Rückmeldung an das Entry-Control sehr vorteilhaft.

Beim Interface

INotifyDataErrorInfo

finde ich zwar recht schön das Einbinden der Evaluierung anhand der zu implementierenden Methoden, aber mir fehlt irgendwie im Kopf noch der Schritt, wie ich direkt das Feedback an das entsprechende Entry bringe? Ich denke, ein Icon an der Seite, im Entry selber oder eine rote Umrandung bzw. etwas in der Art müsste man aus Usability-Gründen schon machen und auch so, dass dieses Fehler-Feedback wieder verschwindet, wenn man den Fehler behoben hat (z.B. bei einem nicht-optionalen String, dass ein Text eingegeben wurde).

Ich weiß jetzt nicht, ob diese Art der Validierung aus dem Link von oben irgendwelche Nachteile hat bzw. auch warum es so viele verschiedene Wege hier gibt. Das verwirrt erst einmal etwas, muss ich zugeben.

12.02.2023 - 20:34 Uhr

Danke für die nützlichen Tipps und Links zum Validieren.
Meine Frage zielte ursprünglich bissl mehr auf das Vorgehen ab mit der foldengen Struktur

  • ContentPage --> deklariert mehrere ContentViews im XAML
  • ContentViews enthalten jeweils ein Entry für die eigentliche Eingabe der Werte
  • ContentView definiert am Entry das DataBinding auf eine Property im Code-Behind der ContentView (eigentlich nur dazu gedacht, von außen die ContentView zu konfigurieren mit dem PlaceHolder, KeyBoard-Setting etc.)
  • ContentPage definiert das DataBinding an jeder definierten ContentView auf einen String im ViewModel der ContentPage (... TextItemOne="{Binding TextItemOneString, Mode=TwoWay}"/>)
  • Ein Button auf der ContentPage hat ein RelayCommand mit Handler im ViewModel der ContentPage, das dann ggf. die Validation der TextItemOneString, TextItemTwoString, ... machen muss (oder eben über eine von Euch vorgeschlagene Validation-Methode)
  • Nach erfolgreicher Validation werden dann die von der ContentView heraus gereichten Entry-Werte aus dem ViewModel RelayCommand an den Datenbank-Service zum Speichern übergeben
12.02.2023 - 07:59 Uhr

Ja super, das scheint es tatsächlich gewesen zu sein! Ich hatte das mal auf einer der BindableProperties ausprobiert, aber da hatte es wohl nicht geholfen, da ich evtl. in einem anderen Scope war und er dann vermutlich nur zur ContentView synchronisiert hätte.

Bin ich generell mit dieser Art der Weitergabe bzw. des Bindings auf der richtigen Seite, oder sollte ich das besser machen? Im Grunde hat ja jede ContentView ihr eigenes Entry, was irgendwohin seinen Wert melden muss.

Gleichzeitig ist die Frage, ob das Validieren, das aus dem Command-Handler des OK-Button angetriggert wird, so gemacht wird, oder ob es da bessere Möglichkeiten gibt.
Im Grunde muss ich hier evaluieren, ob wirklich ein nicht optionales Eingabefeld auch einen Wert hat. So müsste ich ja wieder zurückgemeldet bekommen, ob dieses Feld auch wirklich optional oder mandatory war. Sind dann jeweils wieder eigene Properties, die über die ContentPage von der ContentView aus angebunden werden müssen, an das ViewModel der ContentPage.

11.02.2023 - 07:58 Uhr

Hallo zusammen,

ich habe mir eine ContentView mit Labels und einem Entry zur Eingabe:


<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             x:Class="MyApp.Pages.Views.MyControlView"
             x:Name="this">

    <StackLayout BindingContext="{x:Reference this}">
        <Grid Margin="20, 0, 20, 0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>

            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>


            <StackLayout Grid.Row="0" Grid.Column="0" VerticalOptions="Center">
                <Label Text="{Binding NameLabelString}" />
                <Label Text="{Binding IsOptionalLabelString}" FontSize="12" />
            </StackLayout>

            <StackLayout Grid.Row="0" Grid.Column="1" VerticalOptions="Center" >
                <Entry Text="{Binding TextInput}" Placeholder="{Binding PlaceholderString}" Keyboard="{Binding KeyboardSetting}" Margin="5, 0, 5, 15" />
            </StackLayout>

            <StackLayout Grid.Row="0" Grid.Column="2" VerticalOptions="Center" >
                <Label Text="{Binding BundeslandString}"/>
            </StackLayout>
        </Grid>

       <StackLayout Margin="20, 50, 15, 0">
                <Grid RowSpacing="10">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>

                    <Button Grid.Row="0" Grid.Column="0" Margin="5"
                            Text="OK"
                            Command="{Binding SaveItemCommand}" />

                    <Button Grid.Row="0" Grid.Column="1" Margin="5"
                            Text="Abbrechen"
                            Command="{Binding CancelItemItemCommand}" />
                </Grid>
            </StackLayout>

    </StackLayout>

</ContentView>

Elemente dieser ContentView werden dann in einer anderen ContentPage "instanziiert" bzw. verwendet:


<StackLayout>
            <controls:MyControlView NameLabelString="Name 1:"
                                    IsOptionalLabelString="Erforderlich"
                                    PlaceholderString="Platzhalter für Name 1"
                                    TextInput="{Binding TextInputString1}"/>

            <controls:MyControlView NameLabelString="Name 2:"
                                     IsOptionalLabelString="Optional"
                                     PlaceholderString="Platzhalter für Name 2"
                                     TextInput="{Binding TextInputString2}" />

            ...
</StackLayout>

Die ContentView hat in ihrer Code-Behind-Datei dann BindableProperties, so dass ich von außen zu jeder Verwendung auch Parameter innerhalb der ContentPage weitergeben kann:


public partial class MyControlView : ContentView
{
	public static readonly BindableProperty NameLabelProperty = BindableProperty.Create(nameof(NameLabelString), typeof(string), typeof(MyControlView), string.Empty);
        public static readonly BindableProperty IsOptionalProperty = BindableProperty.Create(nameof(IsOptionalLabelString), typeof(string), typeof(MyControlView), string.Empty);
        ...

        public static readonly BindableProperty TextInputProperty = BindableProperty.Create(nameof(TextInput), typeof(string), typeof(MyControlView), string.Empty);


        public string NameLabelString
         {
                get => (string)GetValue(NameLabelProperty); 
                set => SetValue(NameLabelProperty, value);
         }

        public string IsOptionalLabelString
        {
                get => (string)GetValue(IsOptionalProperty);
                set => SetValue(IsOptionalProperty, value);
         }

         ...

         public string TextInput
         {
                get => (string)GetValue(TextInputProperty);
                set => SetValue(TextInputProperty, value);
          }


         public MyControlView()
         {
                InitializeComponent();
         }
}

Da ich ja jetzt mehrere Instanzen dieses UserControls (also meiner MyControlView) habe, suche ich nach einem Weg, um die Werte aus dem Entry, das darin ist, in das ViewModel der ContentPage zu bringen. Im UI gibt es ja unter diesen MyControlView-Elementen ja noch einen "OK"-Button, der dann die eingegebenen Werte aus dem MyControlView in eine Datenbank speichern soll. Diese [RelayCommand]-Implementierung für das "SaveCommand" habe ich im ViewModel der ContentPage untergebracht. Dort müsste ich auch auf die Werte der Eingaben aus den MyControlView-Entrys zugreifen, um sie in die Datenbank zu bringen.

Ich hab mir gedacht, dass ich in der ContentPage, dort wo die Elemente der MyControlView eingebracht sind, ich dann jeweils Parameter der Entrys habe, die ich dann mittels weiterem DataBinding in das ViewModel der ContentPage bringe:


<StackLayout>
            <controls:MyControlView NameLabelString="Name:"                                                  
                                         IsOptionalLabelString="Erforderlich"
                                         PlaceholderString="Platzhalter Name 1"
                                         [b]TextInput="{Binding TextInputString1}[/b]"/>

           ...
</StackLayout>

Leider klappt das nicht:


public partial class MyContentPageViewModel : ObservableObject
{
        [ObservableProperty]
        private string textInputString1;

        ...

        [RelayCommand]
        async Task SaveItem()
        {
            // auswerten des TextInputString1, TextInputString2, ... und in die Datenbank speichern
        }

        [RelayCommand]
        async Task CancelItem()
        {
            // Just navigate to the previous page
            await Shell.Current.GoToAsync("..");
        }
    }

Ist das ein gängiger Weg, um die Daten aus den Entrys der ContentView in das ViewModel zu bringen, oder gibt es da bessere Mechanismen? Ich müsste im SaveItem eben noch eine Validation der Werte machen, so dass die erforderlichen Elemente auch mit Inputs versorgt sind etc.
Dann klappt das Ganze bei mir aber auch nicht, weil er das DataBinding über TextInput="{Binding TextInputString1}"/> nicht hinbekommt. Im TextInputString1 kommt schlicht weg nix an.

Wie würdet Ihr das Ganze realisieren?

04.02.2023 - 10:16 Uhr

Jetzt habe ich den Fehler gefunden - ist mir fast nicht aufgefallen:

Das MVVM-Toolkit erzeugt ja Code für die als

[RelayCommand]

attributierten Command-Handling-Methoden und baut da als Suffix noch ein "..Command" mit dazu.
Heißt also, mein Command-Handler heißt hier nicht "DeleteItem", sondern "DeleteItemCommand".

Mein Code bei den SwipeItems müsste also wie folgt aussehen:


...
<SwipeItems>
    <SwipeItem Text="Delete" BackgroundColor="Red"
                       Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:Tab2ViewViewModel}}, Path=DeleteItemCommand}"
                       CommandParameter="{Binding .}"/>
</SwipeItems>
...

So kommt er dann über die RelativeSource und den AncestorType mit dem entsprechenden Path auch auf den Command-Handler drauf.

PS: Was mich nur sehr verwirrt hat, ich sehe bei Project > Dependencies > net7.0-android > Analyzers > CommunityToolkit.Mvvm.SourceGenerators > CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator bzw. den anderen Unterkategorien von CommunityToolkit.Mvvm.SourceGenerators nicht immer die Implementierungen, sondern ein schwarzes Symbol und nix weiter. Woran kann das denn liegen?

04.02.2023 - 10:05 Uhr

Okay, denke ein wenig mehr Klarheit konnte ich in meine Umsetzung mit den Routen rein bringen und funktioniert jetzt über das Shell-Objekt direkt dann 🙂


...
await Shell.Current.GoToAsync(nameof(Tab2Details), true);
...

02.02.2023 - 18:27 Uhr

Hallo,

ich habe in einer ContentPage ein wenig mit DataBinding in einem SwipeItem experimentiert und wollte dort ein Delete-Command einrichten, was aber nicht in der Person-Klasse selbst liegt und über das DataTemplate referenziert wird, sondern in es liegt in meiner ViewModel-Klasse "Tab2ViewViewModel".

Über folgenden Code komme ich irgendwie nicht an mein "DeleteItem" ran und weiß auch nicht, wie das ich dem Command-Handler meine aktuell zu löschende Person als Parameter mitgeben kann. Die Person-Klasse liegt unter dem Namespace "MyApp.Models" und die Command-Handler sind in der Klasse "Tab2ViewViewModel", welche im Namespace MyApp.ViewModels liegt.


<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:viewmodel="clr-namespace:MyApp.ViewModels"
             xmlns:models="clr-namespace:MyApp.Models"
             x:DataType="viewmodel:Tab2ViewViewModel"
             x:Class="MyApp.Pages.Tab2View"
             Title="Tab 2 View">

    <ContentPage.ToolbarItems>
        <ToolbarItem IconImageSource="..." Command="{Binding AddNewItemCommand}" />
    </ContentPage.ToolbarItems>

    <Grid RowDefinitions="20, Auto" RowSpacing="10"  Margin="20, 0, 20, 20">
        <SearchBar Grid.Row="0" Grid.Column="0" Grid.RowSpan="5" Placeholder="Tab 2 Item search..." />

        <CollectionView Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Margin="0, 20, 0, 0"
                        ItemsSource="{Binding Items}">

            <CollectionView.ItemTemplate>
                <DataTemplate x:DataType="{x:Type models:Person}">
                    <SwipeView>
                        <SwipeView.RightItems>
                            <SwipeItems>
                                 <SwipeItem Text="Delete" BackgroundColor="Red" 
                                           Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:Tab2ViewViewModel}}, Path=DeleteItem}"
                                           CommandParameter="{Binding .}"/>
                            </SwipeItems>
                        </SwipeView.RightItems>
                        <Grid Padding="0, 5">
                            <Frame>
                                <Label Text="{Binding Name}" FontAttributes="Bold" />
                            </Frame>
                        </Grid>
                    </SwipeView>
                </DataTemplate>
            </CollectionView.ItemTemplate>

        </CollectionView>

    </Grid>

</ContentPage>


using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MyApp.Models;
using System.Collections.ObjectModel;

namespace MyApp.ViewModels
{
    public partial class Tab2ViewViewModel : ObservableObject
    {
        private static int _personCounter = 1;

        [ObservableProperty]
        private ObservableCollection<Person> items;

        [ObservableProperty] 
        private Person personItem;


        public Tab2ViewViewModel()
        {
            items = new ObservableCollection<Person>();
        }


        [RelayCommand]
        private void AddNewItemCommand()
        {
            personItem = new Person();
            personItem.Name = $"Person {_personCounter++}";

            items.Add(personItem);
        }

        [RelayCommand]
        private void DeleteItem(Person item)
        {
            if (items.Contains(item))
            {
                items.Remove(item);
            }
        }
    }
}

02.02.2023 - 18:08 Uhr

Okay, dann werde ich von dem ursprünglichen Vorhaben absehen. Denke, wenn das eher unüblich ist, macht es auch keinen Sinn.

Um ehrlich zu sein, hab ich die Navigation über AppShell dann noch nicht ganz verstanden. Die AppShell übernimmt bei mir die Anordnung in meine Tabs und die entsprechenden Seiten dazu:


<TabBar>
        
        <Tab Title="Tab 1" Icon="...">
            <ShellContent
                ContentTemplate="{DataTemplate pages:Tab1View}"
                Route="Tab1View"
                Shell.PresentationMode="Animated" />
        </Tab>

        <Tab Title="Tab 2" Icon="...">
            <ShellContent           
                ContentTemplate="{DataTemplate pages:Tab2View}"
                Route="Tab2View"
                Shell.PresentationMode="Animated" />
        </Tab>
        
    </TabBar>

Bei mir ist aber so, dass die Tab2View selber im XAML ein Icon in Form eines "+" bereitstellt und daran ein RelayCommand angebunden hat. Wenn der Command-Handler dazu aufgerufen wird, müsste ich erst die Tab2AddNewView instanziieren und laden.

Das würde ich jetzt gedanklich erst einmal nicht im XAML der AppShell.xml definieren, sondern wenn dann würde bei mir das Routing wie oben in der Tab2View.xml angeordnet werden. Aber ich müsste die ja dynamisch erzeugen und darstellen. Ist mir so noch nicht ganz klar, wie ich das mache.

Das von Bernd vorgeschlagene

Navigation.PushModalAsync(new Tab2AddNewView(), true);

kann ich nicht aufrufen. Es gibt bei mir wohl kein Navigation-Objekt und beim PushModalAsync(...) würde bei mir eher ein Tab2AddNewViewViewModel() gefragt werden. Da stimmt noch was nicht ganz...

01.02.2023 - 16:59 Uhr

Hallo zusammen,

ich bin relativ neu in der Entwicklung mit .NET MAUI und war etwas erstaunt über die angebotenen Arten von Pages zu denen man navigieren kann. Auf der Seite https://learn.microsoft.com/en-us/dotnet/maui/user-interface/pages/navigationpage?view=net-maui-7.0 werden hier eigentlich nur wenige Seiten angeboten.

An sich wollte ich von einer ContentPage, die ich über ein Tab aus der AppShell heraus anzeige, an einem "+" Zeichen innerhalb eines ToolBarItem eine Art "Add New"-Dialog einblenden. Vorgestellt hatte ich mir das so, dass dieser, ähnlich zu einer AlertMessage, nur so eingeblendet wird und der Hintergrund der bestehenden ContentPage wird ausgegraut, bis der "Add New"-Dialog geschlossen wird.

Leider tu ich mir mit den Pages aus dem Link oben etwas schwer. Hier würde ja nur die NavigationPage in Frage kommen, die dann das vorhandene Fenster komplett überdeckt, bis diese Page wieder vom Stack runter und geschlossen ist.

Ich will mich natürlich nicht gegen gängige Praxis bei der Steuerung und Navigation in Apps streuben, aber irgendwie weiß ich auch nicht, so ganz gefällt mir das nicht.

Außerdem ist mir auch noch nicht ganz klar, wie ich nach dem Einrichten des Commands auf dem "+"-Button dann auch die Page richtig anzeigen muss. Das Binding zum RelayCommand habe ich über das MVVM-Toolkit gemacht und die Methode zum Reagieren auf das Command dann in die entsprechende AddNewDialogViewModel-Klasse gemacht. Hoffe, das war richtig? Zudem war mir nicht ganz klar, ob ich hier besser ein AsyncRelayCommand nehmen sollte oder nicht. Finde eigentlich, dass es hier nicht unbedingt ein Async braucht, oder?

Wie würdet Ihr Obiges sinnigerweise umsetzen?

30.12.2022 - 07:59 Uhr

Puh, okay, ich glaube ich hab ein Einsehen. An sich dachte ich, ich würde mit doppelt so viel RAM schon hin kommen, jetzt aufgeschraubt und beide Bänke mit 4GB belegt. Bin noch nicht mal sicher, ob da pro Bank auch 8 GB unterstützt werden würden. Dazu noch eine ältere CPU - heißt vielleicht dann wirklich auch, etwas Geld in die Hand nehmen 🙁

Danke schon einmal für Deinen Augen-Öffner...

30.12.2022 - 07:56 Uhr

Hallo Abt,

da bin ich jetzt irgendwie widerum etwas erstaunt und gleichzeitig auch glücklich, dass das schon so gesehen wird.
Ich bin zwar jetzt nicht unzufrieden mit XAML im Allgemeinen, aber stelle doch immer wieder fest, dass es bei der Oberflächengestaltung einfach sehr lange dauert. Das hab ich vor etlichen Jahen mit Java und den Layout-Managern schon so empfunden und hier ging es mir anfangs schon auch wieder so.

Ohne einen Designer mit WYSIWYG dauert das Ganze halt nunmal um ein Zigfaches länger, selbst wenn man eingearbeitet ist.
So gesehen bei Windows Forms war es für mich ein sehr guter Kompromiss. Wobei man da ja schon fast wieder eine Grundsatz-Diskussion lostreten könnte 😁

30.12.2022 - 07:52 Uhr

Hallo Palladin007,

danke, das hat schon einmal geholfen! Das Community Toolkit war auch eine Empfehlung bei den Einstiegs-Videos von James Montemagno zu .NET MAUI.
Das werde ich mir auf alle Fälle noch dazu herunterladen 🙂

29.12.2022 - 12:27 Uhr

Hallo,

ich habe zunächst eine kleine simple Cross-Plattform-Anwendung zum Testen unter .NET MAUI anhand des VS 2022 Projekt-Templates gemacht.

Zum Testen habe ich den Emulator "Pixel 5 - API 33 (Android 13.0 - API 33)". Leider dauert der Compile der Anwendung bis hin zur Anzeige im Emulator wirklich seehr lange, so dass das Einfache Ausprobieren und Ändern der Oberfläche etc. doch recht mühsam ist.

Ich habe einen Notebook mit einer "Intel Core i5 - 5257U CPU @ 2.70 GHz" und Installiertem RAM von 8 GB.

Ist das dafür wirklich so wenig ausreichend, oder geht es einfach nicht schneller bis die App auf dem Emulator sichtbar wird?
Würde hier mehr CPU Power oder RAM helfen?

29.12.2022 - 12:19 Uhr

Hallo,

leider finde ich das Gestalten eines ansprechenden UIs, insbesondere für Apps nicht so ganz easy und bin bereit, dafür ggf. auch ein wenig Geld auszugeben, wenn ich dafür ansprechende Icons und eine zeitgemäße Oberfläche mit ansprechenden Designs bekommen würde.

Hab Ihr hier ein paar Empfehlungen, die in XAML für .NET MAUI geschrieben sind?

29.12.2022 - 12:14 Uhr

Das war ein wichtiger Hinweis, dass das iPhone am Mac selber angeschlossen sein muss. Ohne das funktioniert es leider erst einmal nicht 🙂

13.11.2022 - 17:55 Uhr

Hallo Abt,

wollte nur nochmal Feedback geben, dass es mir jetzt doch gelungen ist, eine erste Xamarin.iOS-Anwendung von Windows aus auf mein iPhone zu bekommen.

Ich hab mich irgendwann geschlagegen gegeben und doch noch einen Developer-Account (nicht den freien Account) gebucht. Der Prozess war aber trotzdem komisch und leider abweichend zu dem was man so im Netz findet. Kam mir fast so, wie veraltete Infos vor im Vergleich zu dem, wie es dann funktioniert mit der neuesten VS 2022 funktioniert hat. Das Xamarin Hot Restart scheint mittlerweile mehr in's VS integriert zu sein.

Was gefühlt recht seltsam war, ist dass man die Anwendung zunächst einmal vom Mac aus deployen musste, um dann das Signing-Zertifikat für die App zu bekommen (inkl. aller Aktivitäten am iPhone selbst zum Vertrauen der App und dem Developer). Meine Idee war zunächst, dass ich das erzeugte Zertifikat vom Mac kopiere und unter Windows dann in die Konfiguration beim Enterprise-Account-Dialog im "iOS Bundle Signing"-Bereich auswähle. Das ist mir so leider nicht gelungen.

Als ich dann den Developer-Account aktiviert hatte, war zumindest mehr drin. Der Prozess war aber für mich immer noch komisch und gewöhnungsbedürftig. Letztlich waren dann noch 2 Probleme zu lösen. Zum einen musste ich in mein Xamarin.iOS-Projekt im "iOS Bundle Signing"-Bereich die Einstellungen in der referenzierten "Entitlements.plist" anpassen. Dort musste der "CFBundleIdentifier" so angepasst werden, wie er zuvor in dem Signing-Prozess auf dem Mac beim allerersten Deployment angegeben war, z.B. "com.xamarin.MyApp".

Leider ging das Deplyoment aber dann immer noch nicht auf mein angeschlossenes iPhone. Der Grund war, dass ich das iPhone per USB an meinen Windows-Rechner angeschlossen hatte. Offenbar muss dieses aber für das Deployment an ein "lokales iPhone" an den Mac angeschlossen sein. Das scheint mir irgendwie unlogisch, wenn ich ehrlich bin, aber so ging es dann:

"Could not install the application on the device AMDeviceSecureInstallApplicationBundle returned: 0xe8008015"
"No valid iOS code signing keys found in keychain. Please enable Automatic Provisioning from the iOS Bundle Signing page."

Meine Vorstellung war halt, dass ich mir für das Kompilieren einen Mac in der Cloud nehme, den für das Kompilieren nutze und dann mein an den Windows-Rechner angeschlossenes iPhone für das Deployment nutze. Das geht aber halt dann erst einmal nicht.

Im Netz wird immer wieder eine Kombination zum lokalen Deployment gezeigt, bei der man verschiedene Dinge benötigt und dann aber lokal auf das iPhone deployen kann: iTunes (vermutlich wegen den Treibern zur Kommunikation mit dem iPhone), Xamarin Hot Restart, einen Mac zum Kompilieren (z.B. auch in einer Cloud) und letztlich dann einfach das iPhone an den lokalen Windows PC angeschlossen.

Hast Du das schon so hinbekommen, oder macht es vielleicht jemand anders gerade so?

Komisch finde ich dann zusätzlich, dass ein Dialog auf ging, der gesagt hat, dass ich die Anwendung nicht im Debug-Build verwenden kann. Musste sie also als Release beauen. Das Debuggen so live ist da tatsächlich auch schwierig.

Wie sind Eure Erfahrungen damit und wie nutzt Ihr das in der Praxis?

Meine Empfehlung nach den Erfahrungen jetzt sind fast: eine mobile Multi-Plattform für Xamarin zu erstellen und dann unter Windows einfach einen Android-Simulator zu nutzen, so dass man die Anwendung einfach lokal besser gestalten und testen kann. Das passt ja dann größtenteils auch für iOS und man kann dann später an einem realen Mac mal das Release deployen.

13.11.2022 - 15:09 Uhr

Okay, dann muss ich mich wohl erst einmal damit zufrieden geben 😉
Vielen Dank für Deine Hilfe!

13.11.2022 - 14:46 Uhr

Hallo Abt,

genau so hatte ich mir das auch schon gedacht 😉
Aber wenn ich einzelne Shoulds habe, dann fällt an sich ja die Verundung weg? Also es könnte sein, dass ein Eintrag ein einzelnes x.B drin hat, was ein anderer auch hat. Nur nicht in der richtigen Kombination. Es müsste da schon sein, dass irgendwie alle Kriterien.

Wäre fast so, als ob ich eine Hintereinanderreihung von mehreren Shoulds brauche (mit Punkt getrennt), was aber wohl nicht zu gehen scheint.

13.11.2022 - 13:44 Uhr

Hallo,

ich habe ein Array und eine Liste mit unterschiedlichem Aufbau. In einem Test möchte ich sicherstellen, dass alle Daten aus dem Array auch in der Liste enthalten sind.
Da es in einem Test läuft, muss es nachtürlich wirken, wie ein Assert. Dazu hatte ich einen Linq-Ausdruck gemacht, der in einer Schleife über das Array und unter Verwendung von ..Should().Be(..) in der Liste fragt, ob das Element vorhanden ist.

Kann man im Fall, dass innerhalb der Verundung des Linq-Ausdrucks eine Property abweicht, ein Kriterium (z.B. x.A == array.A && x.B == array.B && ...) also nicht stimmt, dann auch ausgeben kann, wo es abweicht, also so z.B. x.B == array.B den Unterschied hatte, so dass das Should-Nicht erfüllt werden konnte?

01.11.2022 - 18:29 Uhr

Hallo Abt,

vielen Dank für Deine Antwort und Deine Hilfe. Der Prozess ist wirklich grauslig, wenn ich das mal so sagen darf.
Also, dass es ohne Mac OS gar nicht geht, stört schon auch noch dazu.

Gut, aber ich hatte zumindest einen Teilerfolg. Ich habe auf dem Mac meiner Freundin zumindest direkt auf mein iPhone deployen können. Man musste noch am iPhone selber auch den Entwickler-Modus aktivieren und mir als Deployer der App vertrauen. Soweit so gut. Das ging direkt über XCode auf dem Mac. Die App ist auch auf meinem iPhone und lässt sich auch starten.

Ich wollte nun das Ganze über Windows machen. Dazu hab ich in VS 2022 (17.3.6) die Verbindung zum Mac meiner Freundin hergestellt, damit der Code damit übersetzt wird. Das schaut jetzt zunächst erst einmal gut aus. Beim Laden auf mein iPhone hakt es allerdings.

Solution Platforms: iPhone
Start: OXO's iPhone

Wenn ich dann auf F5 klicke, bekomme ich den Dialog zum "Setup Hot Restart" angezeigt. In dem Dialog klicke ich auf "Next" und sollte dann ein Team auswählen. Hier dachte ich, dass ich mein Apple-ID-Profil als Team auswählen können sollte, da das in der XCode App so auch hinterlegt war.

Leider kann ich da aber gar nix auswählen. In der Drop-Down-Box steht nur "Select a Team..." mehr nicht und nix passiert.

Woran kann das denn liegen?

28.10.2022 - 22:30 Uhr

Hallo,

ich hoffe, dass diese Frage nicht zu dumm ist, aber mich verwirrt das was ich im Internet finde schon etwas, daher wollte ich hier mal fragen, da Ihr ja aktiv Apps entwickelt.

Ich wollte für mein iPhone mal eine kleine App zum Testen erstellen. Ich arbeite unter Windows mit VS 2022 und Xamarin, habe also kein Xcode.

Benötige ich für ein einfaches Deployment und Ausprobieren auf meinem eigenen Handy, ohne Weg über den App-Store, wirklich einen Developer-Account für 99 EUR/Jahr?
Hatte mal was gehört von Xamarin Hot Reload und einfach Anschließen des iPhones. Leider will der da bei mir immer einen Zugang zum Developer Account und ich weiß nicht, ob es wirklich nicht anders geht, oder wie ich ansonsten mal bei mir lokal eine App deployen könnte?

05.10.2022 - 19:12 Uhr

Danke für den Hinweis mit dem Simplex-Algorithmus!

Ich habe das mal mit folgender Implementierung versucht, aber noch etwas Probleme, wie ich meine Constraints richtig machen muss:
https://gist.github.com/trevordixon/9702052

Meine Coeffizienten für x1 ... x4 sind folgende:
Auswahl 1: 0.09 0.069 0.52
Auswahl 2: 0.21 0.02 0.006
Auswahl 3: 0.841 0.011 0.011
Auswahl 4: 0.31 0.17 0.005

Die Constraints für Spalte A, B und C sind folgende:
0.09x1 + 0.21x2 + 0.841x3 + 0.31x4 ≤ 65
0.069x1 + 0.02x2 + 0.011x3 + 0.17x4 ≤ 20
0.52x1 + 0.006x2 + 0.011x3 + 0.005x4 ≤ 45

Die Constraints für x1 ... x4:
x1 ≥ 60 --> Als ≤ Constraint formuliert: -x1 ≤ -60
x1 ≤ 120
x2 ≥ 0 --> Als ≤ Constraint formuliert: -x2 ≤ 0
x2 ≤ 150
x3 ≥ 1 --> Als ≤ Constraint formuliert: -x3 ≤ -1
x3 ≤ 100
x4 ≥ 0 --> Als ≤ Constraint formuliert: -x4 ≤ 0
x4 ≤ 100

Der Fehlerwert bzw. meine Zielfunktion ist eigentlich eine Minimierungsfunktion, muss aber beim Simplex-Algorithmus wohl dann in eine **Maximierungsfunktion **umgebaut werden.

Zunächst die Zielfunktion als Minimierungsfunktion umgeformt:
(65 - (0.09x1 + 0.21x2 + 0.841x3 + 0.31x4)) + (20 - (0.069x1 + 0.02x2 + 0.011x3 + 0.17x4)) + (45 - (0.52x1 + 0.006x2 + 0.011x3 + 0.005x4)) --> Min
(65 - 0.09x1 - 0.21x2 - 0.841x3 - 0.31x4) + (20 - 0.069x1 - 0.02x2 - 0.011x3 - 0.17x4) + (45 - 0.52x1 - 0.006x2 - 0.011x3 - 0.005x4) --> Min
130 - (0.09x1 + 0.069x1 + 0.52x1) - (0.21x2+0.02x2+0.006x2) - (0.841x3+0.011x3+0.011x3) - (0.31x4+0.17x4+0.005x4) --> Min
130 - (0.679x1) - (0.236x2) - (0.863x3) - (0.485x4) --> Min
130 - 0.679x1 - 0.236x2 - 0.863x3 - 0.485x4 --> Min

Daraus mach ich dann für das Kriterium der zu maximierenden Zielfunktion etwas in der Art:
(-1) * (130 - 0.679x1 - 0.236x2 - 0.863x3 - 0.485x4) --> Max
-130 + 0.679x1 + 0.236x2 + 0.863x3 + 0.485x4 --> Max

In dem C#-Algorithmus aus dem Link steht folgendes:


// We are asked to maximize revenue where
//   Revenue = 45*numChairs + 80*numTables
//   Subject to constraints:
//     5*numChairs + 20*numTables <= 400 wood units
//     10*numChairs + 15*numTables <= 450 labor hours
//     numChairs + numTables <= 35 as per Soviet rules
//   Implicit: numChairs >= 0 && numTables >= 0
var s = new Simplex(
    new double[] { 45, 80 }, // revenue for each product
    new double[,] { // constraint coefficients
        { 5, 20 },
        { 10, 15 },
        { 1, 1 },
    },
    new double[] { 400, 450, 35 } // limits
);
// Solution: (maximum: 2100, production: [20, 15], remaining numbers: [0, 25, 0])

Also hätten wir im ersten Array doch irgendwie die Koeffizienten meiner Zielfunktion. Nur, wie mache ich das da mit den "-130"?


var simplexSolver = new Simplex(
                new[] { 0.679, 0.236, 0.863, 0.485 },          // Revenue
                new[,] {
                    // X1 >= 60 and <= 120 Constraints
                    {-1, 0.0, 0.0, 0.0},
                    { 1, 0.0, 0.0, 0.0},
                    
                    // X2 >= 0 and <= 150
                    {0.0, -1, 0.0, 0.0},
                    {0.0,  1, 0.0, 0.0},

                    // X3 >= 1 and <= 100
                    {0.0, 0.0, -1, 0.0},
                    {0.0, 0.0,  1, 0.0},

                    // X4 >= 0 and <= 100
                    {0.0, 0.0, 0.0, -1},
                    {0.0, 0.0, 0.0,  1},

                    // Constraint for Revenue Function as "="-Function
                    // therefore we have to create <= and >= Constraints for it
                    //{-0.679, -0.236, -0.863, -0.4850},
                    //{0.679, 0.236, 0.863, 0.4850},
                },
                new double[] { -60, 120,   0, 150,   -1, 100,   0, 100 }                             // Limits

So kommen aber leider nur Quatsch-Werte raus und ich verstehe aktuell nicht, wo ich schrauben kann, damit das Ergebnis richtig wird.
Eine Lösung hab ich mal über Excel errechnet und da komme ich auf folgende ganzzahliche Ergebnis-Werte:

x1: 84
x2: 144
x3: 8
x4: 66
Fehlerwert bzw. meine Minimierte Zielfunktion: 0.66
Spaltensummen mit den x1 .. x4: A: 64,988 B: 19,984 C: 44,962

29.09.2022 - 11:58 Uhr

Hallo,

ich möchte ein Programm schreiben, das Lösungen für bis zu 10 Gleichungen finden kann (prinzipiell auch denkbar, es auch auf n zu erweitern => das macht aber eher weniger Sinn von der Laufzeit her).

Die Gleichungen sind im Grunde über Constraints definiert:

  • Es gibt eine Auswahl 1 ... Auswahl n
  • Jede Auswahl 1 .. Auswahl n hat einen Datenvektor mit 3 Konstanten, mit denen Werte x1 .. xn multipliziert werden
  • Spaltensumme A ≤ erlaubtes Maximum Spalte A
  • Spaltensumme B ≤ erlaubtes Maximum Spalte B
  • Spaltensumme C ≤ erlaubtes Maximum Spalte C
  • Ein Fehler-Wert ist definiert als Abstand der jeweiligen Spaltensumme zum erlaubten Maximum der Spalte: (Max_A - Summe_Spalte_A) + (Max_B - Summe_Spalte_B) + (Max_C - Summe_Spalte_C)
  • Aufgabe: Finde x1 .. xn, so dass der Fehler-Wert möglichst gering ist

Als Beipiel die Datenvektoren für eine Ausahl:

  • Auswahl 1 = (0.01 0.04 0.332)
  • Auswahl 2 = (0.21 0.5 0.12)
  • ...
  • Auswahl n = (0.71 0.24 0.332)
  • Die Werte aus Spalte A, B und C ergeben sich immer durch Multiplikation des Datenvektors mit dem zu findenden x1 ... xn

Dann ergiben sich Constraints etwa in der Form:

  • Constraint 1: Spaltensumme A ≤ Max_A

  • Constraint 2: Spaltensumme B ≤ Max_B

  • Constraint 3: Spaltensumme C ≤ Max_C

  • x1 .. xn sind ganzzahlig

  • x1 muss zwischen .. und .. sein (diese Mindest und Höchstwerte muss man vorgeben und sind ≥0)

  • x2 muss zwischen .. und .. sein (diese Mindest und Höchstwerte muss man vorgeben und sind ≥0)

  • x3 muss zwischen .. und .. sein (diese Mindest und Höchstwerte muss man vorgeben und sind ≥0)

  • Fehlerwert: (Max_A - (0.01x1 + 0.04x2 + ... + 0.332xn)) + (Max_B - (0.21x1 + 0.6x2 + ... + 0.12xn)) + (Max_C - (0.71x1 + 0.24x2 + ... + 0.332*xn))

  • Finde x1 ... xn, so dass der Fehlerwert möglichst minimal wird (sofern es eine Lösung gibt!)

Wie könnte man denn möglichst performant so ein System über einen C#-Algorithmus lösen lassen?

23.01.2021 - 16:44 Uhr

Okay, überzeugt 😉

Bin trotzdem froh, dass wir diesen Thread hatten! Danke Euch allen, dass Ihr Euch dazugeschalten habt!

23.01.2021 - 15:14 Uhr

@Oxo: Du hast Enums (immer noch) nicht verstanden!

Wieso meinst Du? 😃
Finde schon, dass man bei den Enums auch zwischen Wert und dem eigentlichen Namen unterscheiden können muss - irgendwie zumindest (kopf durch die wand)

23.01.2021 - 13:49 Uhr

Nicht unbedingt als String. Der Enum-Wert mit dem richtigen Text, wäre mir lieber, daher auch ein Typ des Enums als return-Value

23.01.2021 - 13:07 Uhr

Das hatte ich auch schon. Aber hilft nicht so viel, aus 2 Gründen, zum einen, könnte ja jemand auf die Idee kommen und das ganze Flag umbenennen, dann würde der String-Vergleich auch nicht mehr gehen. Zum anderen, löst sich das nicht in das richtige Flag des Enums auf, obwohl die Namen anders sind, vergleiche sind ja nur die Namen.

Im Debugger siehst du den Aufruf von
>
(s. bes. unter "Hinweise")!
Bei mehreren Enum-Namen mit gleichem Wert (Value) wird also irgendeiner dieser Namen ausgegeben!

Hhm, dann kann es wohl gar nicht gehen, oder bleibt ein Zufallsprodukt, wie Du schreibst 😦

Siehe hier:

23.01.2021 - 12:46 Uhr

Also das ist schon ein witziges Spielchen und ich sitze auf dem Schlauch.
Wenn ich das so versuche, sehe ich im Debugger zunächst verschiedene Namen in dem Array aus GetNames(..).

In einer foreach-Schleife auch noch bei der "names"-Auflistung. Kommt es aber dann zu der Zuweisung zur Variablen "name" in der foreach, dann steht da plötzlich 2x "Latest" drin


var names = Enum.GetNames(typeof(Versions));
foreach (var name in names)
{
    var parsedValue = (Versions)Enum.Parse(typeof(Versions), name);   // Cast zwar unnötig, aber zur Sicherheit auch ausprobiert
    if (parsedValue == latestVersion && !name.Equals(latestVersion.ToString()))
        return parsedValue;
}

23.01.2021 - 12:04 Uhr

Eigentlich brauche ich alle Enum-Werte mit demselben int-Wert, wie auch immer ich das per Link sagen muss, und dann eben nicht den, der denselben Namen hat, wie der Name des vorgegebenen Enum-Werts "Latest"

Schaue ich in den Debugger, zeigt der mir bei allem, auch bei GetValues mit OrderBy immer 2x Latest an, was ich schon seltsam finde, ehrlich gesagt.

23.01.2021 - 10:17 Uhr

Also Abt hatte Recht mit dem was er schreibt.
Die Versionen sind mit bestimmter Hardware verknüpft, die an anderer Stelle über passende Dateien eingepflegt und gewartet werden (das auch noch in verschiedenen Formaten, aber gut). Eine neuere Version wird also erst einmal als Master dort festgestellt. Intern kann man verschiedene nutzen, aber wie man sieht, muss das Enum über diesen Mechanismus dann immer nachgepflegt werden.

Das was ich mit Versions geschrieben hatte, war nur das Grundprinzip, um ein einfaches Beispiel für hier zu haben.

Auch die Kommentare, dass das ganze System Fehleranfällig ist, sehe ich genauso. Aktuell fällt mir nur nicht ein, wie ich sonst aus der Misere kommen könnte, wenn mehrere Stellen beteiligt sind und jeder Seins macht.

Würde sowas wie hier verlässlich funktionieren?
Also sprich, such mir alle Werte, die dem der latestVersion entsprechen und da es bei den Enums ja nur 2 sein können, nimm einfach das letzte aus der Liste.
Oder wird das zufällig ausgegeben dann?


private Versions GetLatestVersionAssigned(Versions latestVersion)
{
    var latestVersionAssigned = Enum.GetValues(typeof(Versions)).Cast<Versions>().Where(version => version == latestVersion).Last();
    return latestVersionAssigned;
}

21.01.2021 - 17:30 Uhr

Hallo zusammen,

in einem enum gibt es verschiedene Werte in der Art:


enum Versions
{
    V1,
    V2,
    V3,
    Latest = V3,
}

In einem Test wird aus einer Master-Datei die aktuellste Version ermittelt.
Stellt sich raus, dass es bereits eine "V4" gibt, aber im enum "Latest" immer noch die "V3" eingestellt ist, dann wird der Test rot.

Im Assert des Tests wird die ausgelesene "V4" natürlich gegen "Lastest" geprüft.
Stimmen die beiden Versionen nicht überein, gibt NUnit folgendes aus:

Expected: V4
But was: Latest

Ich würde das aber gerne anders ausgeben und dort die Enum-Alternative als String haben:

Expected: V4
But was: V3

Wie könnte ich das hinbekommen?

06.01.2021 - 16:08 Uhr

Also für meinen Fall hab ich die besten Ergebnisse doch mit dem TextRenderer. Falls jemand ähnliche "Probleme" hat, hier der Code im DrawItem, der für mich funktioniert:


private void dataGridViewComboBoxCell_DrawItem(object sender, DrawItemEventArgs e)
{
	if (e.Index == -1)
		return;

	e.DrawBackground();
	
	var comboBoxEditingControl = sender as DataGridViewComboBoxEditingControl;
	if (comboBoxEditingControl == null)
		return;

	Brush brush;
	if (comboBoxEditingControl.DroppedDown)
	{
		if ((e.State & DrawItemState.Focus) == DrawItemState.Focus)
		{
			brush = new SolidBrush(System.Drawing.Color.Transparent);
			e.Graphics.FillRectangle(brush, e.Bounds);
		}
		else
		{
			brush = new SolidBrush(System.Drawing.Color.White);
			e.Graphics.FillRectangle(brush, e.Bounds);
		}
	}
	else
	{
		brush = new SolidBrush(System.Drawing.Color.White);
		e.Graphics.FillRectangle(brush, e.Bounds);
	}


	var dataRowView = comboBoxEditingControl.Items[e.Index] as DataRowView;
	if (dataRowView == null)
		return;

	System.Drawing.Color fontColor;
	string itemText = dataRowView.Row.ItemArray[0].ToString();
	if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
	{
		if (comboBoxEditingControl.DroppedDown)
		{
			fontColor = System.Drawing.Color.White;
		}
		else
		{
			fontColor = System.Drawing.Color.Black;
		}
	}
	else
	{
		fontColor = System.Drawing.Color.Black;
	}

	var cellBounds = e.Bounds;
	cellBounds.X -= 2;
	cellBounds.Y -= 2;

	TextRenderer.DrawText(e.Graphics, itemText, 
		dataGridView.DefaultCellStyle.Font, 
		cellBounds, fontColor, 
		TextFormatFlags.NoPrefix | TextFormatFlags.VerticalCenter);
}

05.01.2021 - 21:33 Uhr

So, nach Experimentieren bin ich weiter gekommen. Ob das jetzt schön ist, darüber kann man auch geteilter Meinung sein, aber zumindest ein FlatStyle hat ein bisschen was von dem grauen Hintergrund aufgelöst und auch der Pfeil an der ComboBox wird erst einmal nicht mehr per Default angezeigt, sondern erst wenn ich den Fokus in die Zelle setze.


DataGridViewComboBoxCell comboBoxCell = new DataGridViewComboBoxCell();
...
comboBoxCell.DisplayStyle = DataGridViewComboBoxDisplayStyle.Nothing;
comboBoxCell.Style.SelectionBackColor = System.Drawing.Color.White;
comboBoxCell.FlatStyle = FlatStyle.Flat;

Dann waren noch die weiteren Farben und das Highlightings im DropDown ein Problem. Per Default war die Zelle, wenn sie fokussiert war, mit einem blauen Balken und störendem Focus-Rectangle ausgestattet. Mit Selbstzeichnen im DrawItem-Event konnte ich das aufgelösen:


private void dataGridView_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
	ComboBox cb = e.Control as ComboBox;
	if (cb != null)
	{
		cb.DropDownStyle = ComboBoxStyle.DropDownList;

		cb.DrawMode = DrawMode.OwnerDrawFixed;
		cb.DrawItem -= this.dataGridViewComboBoxCell_DrawItem;
		cb.DrawItem += this.dataGridViewComboBoxCell_DrawItem;
	}
}


private void dataGridViewComboBoxCell_DrawItem(object sender, DrawItemEventArgs e)
{
	if (e.Index == -1)
		return;

	e.DrawBackground();
	
	var comboBoxEditingControl = sender as DataGridViewComboBoxEditingControl;
	if (comboBoxEditingControl == null)
		return;

	Brush brush;
	if (comboBoxEditingControl.DroppedDown)
	{                
		if ((e.State & DrawItemState.Focus) == DrawItemState.Focus)
		{
			brush = new SolidBrush(e.BackColor);
			e.Graphics.FillRectangle(brush, e.Bounds);
		}
		else
		{
			brush = new SolidBrush(System.Drawing.Color.White);
			e.Graphics.FillRectangle(brush, e.Bounds);
		}
	}
	else
	{
		brush = new SolidBrush(System.Drawing.Color.White);
		e.Graphics.FillRectangle(brush, e.Bounds);
	}
			   

	var dataRowView = comboBoxEditingControl.Items[e.Index] as DataRowView;
	if (dataRowView == null)
		return;

	string itemText = dataRowView.Row.ItemArray[0].ToString();
	if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
	{
		if (comboBoxEditingControl.DroppedDown)
		{
			brush = Brushes.White;
		}
		else
		{
			brush = Brushes.Black;
		}
	}
	else
	{
		brush = Brushes.Black;
	}
				
	var font = new System.Drawing.Font("Calibri", 11);
	e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
	//e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
	e.Graphics.DrawString(itemText, font, brush, e.Bounds.X-1, e.Bounds.Y-1);
}

Wo ich noch verrückt werde, ist tatsächlich das Zeichnen des Textes mittetls DrawString.
Der AntiAlias bzw. AntiAliasGridFit haben zwar Veränderungen zum Positiven gebracht, aber der Text im fokussierten Zustand schaut trotzdem in der Vergrößerung anders aus. Im einen scheint es einen farbigen Schatten zu geben, wo im anderen die Farben auch fehlen. Ob das wirklich der Unterschied ist, oder was anderes, kann ich nicht sagen. Der Benutzer sieht, dass es zwischen Fokussiertem und nicht Fokussierten Zustand eine Änderung im Text gibt.

Ich hab schon mal ausprobiert, den Text mit dem TextRenderer.DrawString(...) zu zeichnen, aber auch da schaut das etwas komisch aus.

Habt Ihr mir noch Tipps, was ich machen kann, damit die Schrift in der selektierten DataGridViewComboBoxCell, der nicht selektierten und auch den anderen Zellen gleich gerendert werden? Hab ich da noch einen Einflussfaktor, oder muss ich mit selber Zeichnen damit leben?

04.01.2021 - 15:45 Uhr

Muss gestehen, das hab ich jetzt leider noch nicht verstanden.

"Unused Spaced" habe ich in den Properties des DataGridView oder der DataGridViewComboBoxCell leider nicht gefunden. (?)

Die BackgroundColor-Eigenschaft des DataGridView hatte ich eigentlich schon auf White gesetzt, aber hat leider nicht den gewünschten Effekt auf die DataGridViewComboBoxCell gebracht. Die ComboBoxCell darunter bleibt leider grau und nicht weiß.

04.01.2021 - 13:39 Uhr

Hallo zusammen,

ich hab in einem DataGridView eine Spalte mit DataGridViewComboBoxCell's


dataGridView.CellBorderStyle = DataGridViewCellBorderStyle.Single;
dataGridView.DefaultCellStyle.BackColor = System.Drawing.Color.White;
dataGridView.DefaultCellStyle.SelectionBackColor = System.Drawing.Color.White;
dataGridView.DefaultCellStyle.SelectionForeColor = System.Drawing.Color.Black;
dataGridView.BackgroundColor = System.Drawing.Color.White;
dataGridView.SelectionMode = DataGridViewSelectionMode.CellSelect;
dataGridView.EditMode = DataGridViewEditMode.EditOnEnter;


DataGridViewComboBoxCell comboBoxCell = new DataGridViewComboBoxCell();
...
comboBoxCell.Style.BackColor = System.Drawing.Color.White;
comboBoxCell.DisplayStyle = DataGridViewComboBoxDisplayStyle.Nothing;
comboBoxCell.Style.SelectionBackColor = System.Drawing.Color.White;

Mich wundert es, warum per Default meine ComboBox so grau angezeigt wird, obwohl ich überall mit Weiß im Hintergrund arbeite. Auch der Innenraum stellt sich mir beim Ändern des Backgrounds an anderer Stelle nicht auf weiß.

Die Optik aus dem Screenshot links fände ich auch ganz schön, aber egal wo ich Properties ändere, das ändert sich nicht in so ne Optik. Was sollte ich denn da noch setzen, dass so ne schöne Optik raus kommt?

Ebenfalls würde mir gefallen, wenn nach dem Klick in eine Zelle, der Pfeil des DropDowns nach rechts in die Nachbarzelle verschoben ist (also das gesamte DropDown breiter). Finde, das gibt nen schönen optischen Effekt. Ich hab schon mit DisplayEditingControl der Cell etc. im CellPainting-Ereginis herum experimentiert aber es gelingt mir nicht, das zu machen, außerdem flackert es dabei im GridView extrem.

Wie könnte ich das DropDown denn Breiter malen, so dass es aus der Zelle rechts noch ein Stück raus ragt bzw. auch schön in der Zelle selbst eingepasst ist. Aktuell wirkt es ja so, als ob es leicht nach unten verschoben ist, wenn es den Fokus hat? Wo müsste ich schrauben, damit ich da was beeinflussen kann?

03.01.2021 - 22:19 Uhr

Das kannst du über NuGet zu deinem Projekt hinzufügen:
>

Ahh, danke für den Hinweis! Da hatte ich noch gar nicht geschaut.

03.01.2021 - 12:57 Uhr

Zeichne doch besser den Doppel-Rahmen nur innerhalb einer Zelle (an Excel solltest du dich nicht 1:1 orientieren).

Also Du meinst, die Zelle nach innen verkleinern? Aktuell lasse ich ja den Innenraum der Zelle gleich groß und male die Linien nach außen hin

Hast du dir denn jetzt schon mal das SourceGrid angeschaut?

Ja, auf die Seite bin ich gegangen und habe es mir auch mal heruntergeladen, aber war ehrlich gesagt nicht in der Lage, das zum Laufen zu bringen (https://archive.codeplex.com/?p=sourcegrid). Fertig gebaute Releases habe ich nicht gesehen, um es gleich zu benutzen. Dann wollte ich das über die *.sln öffnen (mit VS 2017 und auch VS 2019), aber da kommen Fehler.

Wie hast Du das gebaut bzw. zum Laufen bekommen?

03.01.2021 - 10:27 Uhr

In der Zwischenzeit hatte ich das Malen schon mal fertig gemacht, aber ja, es gibt komische Effekte, wie Du sie beschrieben hast.

Im Screenshot sieht man links wie der Effekt ist, wenn ich auf eine Zelle klicke. Schaut alles sauber aus soweit. Rechts sieht man, was passiert, wenn ich mit den Cursor-Tasten herum navigiere.

Kann man diesen von Dir beschriebenen Effekt nicht los werden?
Auch beim ersten Laden des DataGridViews sind Zellen noch nicht gemalt und etwas zerhexelt. Denke mir immer, die Profi-Tools (wie z.B. Excel) müssen es ja auch irgendwie sauber zeichnen.


if (e.ColumnIndex == dataGridView.CurrentCell.ColumnIndex - 1 && e.RowIndex == dataGridView.CurrentCell.RowIndex - 1)
{
	// Cell left to selected cell row above
	e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);

	using (SolidBrush solidBrush = new SolidBrush(System.Drawing.Color.Red))
	{
		e.Graphics.FillRectangle(solidBrush, e.CellBounds.X + e.CellBounds.Width - 2, e.CellBounds.Y + e.CellBounds.Height - 2, 1, 1);
	}

	e.Handled = true;
}
else if (e.ColumnIndex == dataGridView.CurrentCell.ColumnIndex && e.RowIndex == dataGridView.CurrentCell.RowIndex)
{
	// Selected Cell
	e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);

	using (Pen pen = new Pen(System.Drawing.Color.Red, 3))
	{
		Rectangle rect = e.CellBounds;
		rect.X -= 1;
		rect.Y -= 1;
		e.Graphics.DrawRectangle(pen, rect);
	}

	using (Pen pen = new Pen(System.Drawing.Color.White, 1))
	{
		// Top, Bottom
		e.Graphics.DrawLine(pen, e.CellBounds.X - 2, e.CellBounds.Y - 1, e.CellBounds.X + e.CellBounds.Width, e.CellBounds.Y - 1);
		e.Graphics.DrawLine(pen, e.CellBounds.X - 2, e.CellBounds.Y + e.CellBounds.Height - 1, e.CellBounds.X + e.CellBounds.Width, e.CellBounds.Y + e.CellBounds.Height - 1);
	}

	using (Pen pen = new Pen(System.Drawing.Color.White, 1))
	{
		// Left
		e.Graphics.DrawLine(pen, e.CellBounds.X - 1, e.CellBounds.Y - 2, e.CellBounds.X - 1, e.CellBounds.Y + e.CellBounds.Height);
	}

	using (Pen pen = new Pen(System.Drawing.Color.White, 1))
	{
		// Right
		e.Graphics.DrawLine(pen, e.CellBounds.X + e.CellBounds.Width - 1, e.CellBounds.Y - 2, e.CellBounds.X + e.CellBounds.Width - 1, e.CellBounds.Y + e.CellBounds.Height);
	}

	e.Handled = true;
}
else if (e.ColumnIndex == dataGridView.CurrentCell.ColumnIndex + 1 && e.RowIndex == dataGridView.CurrentCell.RowIndex)
{
	// Cell right to selected cell, same row
	e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);

	using (Pen pen = new Pen(System.Drawing.Color.Red, 2))
	{
		e.Graphics.DrawLine(pen, e.CellBounds.X, e.CellBounds.Y, e.CellBounds.X, e.CellBounds.Y + +e.CellBounds.Height);
	}

	e.Handled = true;
}
else if (e.ColumnIndex == dataGridView.CurrentCell.ColumnIndex - 1 && e.RowIndex == dataGridView.CurrentCell.RowIndex + 1)
{
	// Cell left to selected cell, row below
	e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);

	using (SolidBrush solidBrush = new SolidBrush(System.Drawing.Color.Red))
	{
		e.Graphics.FillRectangle(solidBrush, e.CellBounds.X + e.CellBounds.Width - 2, e.CellBounds.Y, 1, 1);
	}

	e.Handled = true;
}
else if (e.ColumnIndex == dataGridView.CurrentCell.ColumnIndex && e.RowIndex == dataGridView.CurrentCell.RowIndex + 1)
{
	// Cell below selected cell, same column
	e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);

	using (Pen pen = new Pen(System.Drawing.Color.Red, 2))
	{
		e.Graphics.DrawLine(pen, e.CellBounds.X, e.CellBounds.Y, e.CellBounds.X + e.CellBounds.Width, e.CellBounds.Y);
	}

	e.Handled = true;
}
else if (e.ColumnIndex == dataGridView.CurrentCell.ColumnIndex + 1 && e.RowIndex == dataGridView.CurrentCell.RowIndex + 1)
{
	// Cell right to selected cell, row below
	e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);

	using (SolidBrush solidBrush = new SolidBrush(System.Drawing.Color.Red))
	{
		e.Graphics.FillRectangle(solidBrush, e.CellBounds.X, e.CellBounds.Y, 1, 1);
	}

	using (SolidBrush solidBrush = new SolidBrush(System.Drawing.Color.White))
	{
		e.Graphics.FillRectangle(solidBrush, e.CellBounds.X - 1, e.CellBounds.Y, 1, 1);
	}

	using (SolidBrush solidBrush = new SolidBrush(System.Drawing.Color.White))
	{
		e.Graphics.FillRectangle(solidBrush, e.CellBounds.X, e.CellBounds.Y - 1, 1, 1);
	}

	e.Handled = true;
}

03.01.2021 - 08:04 Uhr

Hier der Effekt, wenn man die Zellen rechts daneben und darunter zeichnen lässt.
Das einzige was da noch fehlt, sind praktisch die Punkte der Dicke 1 in der rechten/linken ecke der Zelle, die links bzw. rechts unterhalb der Spalte vor der selektierten Spalte gezeichnet werden.

Also schon lustig das alles und ich glaube aktuell, dass mit meiner Vermutung richtig liege, dass das Grid von links oben nach rechts unten gezeichnet wird, und dass es daher bei den Zellen oberhalb bzw. links davon bis zur selben Row möglich war in diese Zellen rein zu malen und bei denen rechts bzw. darunter das Gemalte einfach später dann wieder übermalt wird, wenn diese Zellen gezeichnet werden.


if (e.ColumnIndex == dataGridView.CurrentCell.ColumnIndex + 1 && e.RowIndex == dataGridView.CurrentCell.RowIndex)
{
	// Painting for Cell right next to the selected cell
	e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);

	using (Pen pen = new Pen(System.Drawing.Color.Red, 2))
	{
		e.Graphics.DrawLine(pen, e.CellBounds.X, e.CellBounds.Y, e.CellBounds.X, e.CellBounds.Y + +e.CellBounds.Height);
	}

	e.Handled = true;
}
else if (e.ColumnIndex == dataGridView.CurrentCell.ColumnIndex && e.RowIndex == dataGridView.CurrentCell.RowIndex)
{
	// Painting for selected Cell
	e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);

	using (Pen pen = new Pen(System.Drawing.Color.Red, 3))
	{
		Rectangle rect = e.CellBounds;
		rect.X -= 1;
		rect.Y -= 1;
		e.Graphics.DrawRectangle(pen, rect);
	}

	e.Handled = true;
}
else if (e.ColumnIndex == dataGridView.CurrentCell.ColumnIndex && e.RowIndex == dataGridView.CurrentCell.RowIndex + 1)
{
	// Painting for cell below selected Cell
	e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);

	using (Pen pen = new Pen(System.Drawing.Color.Red, 2))
	{
		e.Graphics.DrawLine(pen, e.CellBounds.X, e.CellBounds.Y, e.CellBounds.X + e.CellBounds.Width, e.CellBounds.Y);
	}

	e.Handled = true;
}

02.01.2021 - 21:33 Uhr

Das ist zwar richtig, aber das war erst mal Absicht und mich wundert es, dass es nicht nach unten raus gezeichnet wird. Es müsste ja unten über die Bottom-Linie raus laufen.

Die ersten Versuche mit DrawLine sind zwar besser, aber auch rechts raus wird die untere Linie nicht über den Rand in die Zelle rechts rein gezeichnet, obwohl ich da mal mit + 100 nen richtig langen Strich rein gezeichnet hab (hier mal für den Test mit roter Linie)


// Painting for selected Cells
e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);

// Top-Line
using (Pen pen = new Pen(System.Drawing.Color.Black, 3))
{
	int offset_X = 3;
	int offset_Y = -2;
	e.Graphics.DrawLine(pen, e.CellBounds.X + offset_X, e.CellBounds.Y + offset_Y, e.CellBounds.X - offset_X + e.CellBounds.Width, e.CellBounds.Y + offset_Y);                    
}

// Bottom-Line
using (Pen pen = new Pen(System.Drawing.Color.Black, 3))
{
	int offset_X = 3;
	int offset_Y = -2;
	e.Graphics.DrawLine(pen, e.CellBounds.X + offset_X, e.CellBounds.Y + offset_Y + e.CellBounds.Height, e.CellBounds.X + offset_X + e.CellBounds.Width, e.CellBounds.Y + offset_Y + e.CellBounds.Height);
}

using (Pen pen = new Pen(System.Drawing.Color.Black, 2))
{
	int offset_X = 3;
	int offset_Y = -2;
	e.Graphics.DrawLine(pen, e.CellBounds.X + offset_X, e.CellBounds.Y + offset_Y + e.CellBounds.Height, e.CellBounds.X + offset_X + e.CellBounds.Width, e.CellBounds.Y + offset_Y + e.CellBounds.Height);

	e.Graphics.DrawLine(pen, e.CellBounds.X + 1, e.CellBounds.Y - 2, e.CellBounds.X + 1, e.CellBounds.Y + e.CellBounds.Height + 2);
	e.Graphics.DrawLine(pen, e.CellBounds.X + e.CellBounds.Width - 2, e.CellBounds.Y - 2, e.CellBounds.X + e.CellBounds.Width - 2, e.CellBounds.Y + e.CellBounds.Height + 2);
}

using (Pen pen = new Pen(System.Drawing.Color.Red, 1))
{
	int offset_X = -3;
	int offset_Y = -1;
	// Top, Bottom
	e.Graphics.DrawLine(pen, e.CellBounds.X + offset_X, e.CellBounds.Y + offset_Y, e.CellBounds.X + e.CellBounds.Width + 2, e.CellBounds.Y + offset_Y);
	e.Graphics.DrawLine(pen, e.CellBounds.X + offset_X, e.CellBounds.Y + offset_Y + e.CellBounds.Height, e.CellBounds.X + e.CellBounds.Width + 100, e.CellBounds.Y + offset_Y + e.CellBounds.Height);
}

using (Pen pen = new Pen(System.Drawing.Color.Red, 1))
{
	int offset_X = -3;
	int offset_Y = -2;
	// Left, Right
	e.Graphics.DrawLine(pen, e.CellBounds.X, e.CellBounds.Y + offset_Y, e.CellBounds.X, e.CellBounds.Y - offset_Y + e.CellBounds.Height);
	e.Graphics.DrawLine(pen, e.CellBounds.X + e.CellBounds.Width - 2, e.CellBounds.Y + offset_Y, e.CellBounds.X + e.CellBounds.Width - 2, e.CellBounds.Y + e.CellBounds.Height + 2);
}

e.Handled = true;

Gibt es dafür eine Erklärung, dass das abgeschnitten wird? Liegt das zufällig daran, weil die nachfolgenden Zellen rechts und dann nach unten hin, alle ja auch durch das CellPainting laufen und damit dann irgendwas über meine Styles überschrieben wird?

02.01.2021 - 18:06 Uhr

Aus deinem Bild entnehme ich, dass die Startkoordinate (X,Y) nicht passt.

Ein bisschen Verschieben und die Größe anpassen, dann sollte es gut sein.

Das dachte ich auch, aber leider nicht ganz. Denn ändere ich mal nur beim äußeren Rechteck die Y-Koordinate ab, dass es nach unten geht und belasse das Rechteck bei, schneidet der das am unteren Rand der Zelle einfach ab, als ob die Zelle selbst der Begrenzer ist und es da dann gekürzt wird:


// Painting for selected Cells
e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);

using (Pen pen = new Pen(System.Drawing.Color.Black, 4))
{
	Rectangle rect = e.CellBounds;
	rect.Y += 2;
	rect.Width -= 2;
	rect.Height -= 2;
	e.Graphics.DrawRectangle(pen, rect);
}

//using (Pen pen = new Pen(System.Drawing.Color.White, 2))
//{
//    Rectangle rect = e.CellBounds;
//    //rect.X += 2;
//    //rect.Y += 2;
//    rect.Width -= 1;
//    rect.Height -= 1;
//    e.Graphics.DrawRectangle(pen, rect);
//}

e.Handled = true;

Erwartet hätte ich, dass sich das ganze Rechteck über die Zelle drüber legt.

02.01.2021 - 14:30 Uhr

Die Koordinaten und die Dicke passen prinzipiell glaub. Nur, das Problem ist, dass im Screenshot in der Mitte ein weißer Strich/Bereich frei liegt und das bekomme ich irgendwie nicht hin.

Welchen Brush müsste ich denn dazu verwenden? Der Effekt beim Hatch-Brush war nur, dass meine ganze Linie innen gestrichelt war, aber nicht so in dem Effekt "Aussen-/Innen-Linie"


// Painting for selected Cells
e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);

using (Pen pen = new Pen(System.Drawing.Color.Black, 4))
{
	Rectangle rect = e.CellBounds;
	rect.Width -= 2;
	rect.Height -= 2;
	e.Graphics.DrawRectangle(pen, rect);
}

using (Pen pen = new Pen(System.Drawing.Color.White, 2))
{
	Rectangle rect = e.CellBounds;
	rect.Width -= 2;
	rect.Height -= 2;
	e.Graphics.DrawRectangle(pen, rect);
}

e.Handled = true;

Also irgendwie rechts drüben und unten will es nicht in die anderen Zellen rüber ragen.

Ein rect.Width += 2; rect.Height += 2; scheint es aber auch nicht wirklich zu bringen.

02.01.2021 - 12:53 Uhr

Hallo,

ich würde gerne im DataGridView eine Doppel-Linien-Umrandung, wie im Screenshot in der aktuell selektierten Zelle zeichnen.

An sich dachte ich, dass ich das über einen Hatch-Brush realisieren kann, diesen dann dem Pen übergeben, aber das hat leider nicht so hingehauen, daher erst einmal wieder raus genommen.

Was mach ich denn beim Zeichnen falsch, dass das nur ein dicker Rahmen, statt Doppel-Linie ist?


private void dataGridView_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
	if (e.ColumnIndex == dataGridView.CurrentCell.ColumnIndex && e.RowIndex == dataGridView.CurrentCell.RowIndex)
	{                
		// Painting for selected Cells
		e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);

		using (Pen pen = new Pen(System.Drawing.Color.Red, 4))
		{
			Rectangle rect = e.CellBounds;
			rect.Width -= 2;
			rect.Height -= 2;
			e.Graphics.DrawRectangle(pen, rect);
		}

		e.Handled = true;
	}
}

01.01.2021 - 10:58 Uhr

Der Fehler war schon hunderte Male im Forum. Daher bitte das nächste Mal zuerst die Forensuche nutzen. Danke 😉

Hallo Abt,

die hatte ich schon gelesen und gesehen, dass es am x86/x64 vermutlich auch hier liegt. Vielleicht einfach die Frage, ob es eine elegante Lösung gibt.

Ich habe es nun so gelöst, dass ich auch das *.xlsx über das OpenDocument SDK einlese


using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using System.Data;
...

private DataTable ReadExcelSheetToDataTable(string pathExcelFile)
{
	DataTable dt = new DataTable();
	
	dt.Columns.Add("Column 1");
	dt.Columns.Add("Column 2");
	dt.Columns.Add("Column 3");
	dt.Columns.Add("Column 4");
	dt.Rows.Add();  // First of all add an Empty Row

	using (SpreadsheetDocument doc = SpreadsheetDocument.Open(pathExcelFile, false))
	{
		// Read the first Sheet
		Sheet sheet = doc.WorkbookPart.Workbook.Sheets.GetFirstChild<Sheet>();
		Worksheet worksheet = (doc.WorkbookPart.GetPartById(sheet.Id.Value) as WorksheetPart).Worksheet;
		IEnumerable<Row> rows = worksheet.GetFirstChild<SheetData>().Descendants<Row>();
		
		int counter = 0;
		foreach (Row row in rows)
		{
			counter = counter + 1;

			// Skip first 3 rows as header
			if (counter >= 3)
			{                        
				dt.Rows.Add();
				int i = 0;
				foreach (Cell cell in row.Descendants<Cell>())
				{
					dt.Rows[dt.Rows.Count - 1][i] = GetCellValue(doc, cell);
					i++;
				}
			}
		}

	}

	return dt;
}

private string GetCellValue(SpreadsheetDocument doc, Cell cell)
{
	string cellValueText = cell.CellValue.InnerText;
	Double value = Double.Parse(cellValueText, NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture) / 100;

	if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString)
	{
		return doc.WorkbookPart.SharedStringTablePart.SharedStringTable.ChildElements.GetItem(int.Parse(cellValueText)).InnerText;
	}
	return value.ToString();
}

31.12.2020 - 14:56 Uhr

Hallo zusammen,

leider hakelt es bei mir und meiner Anwendung wohl am laufenden Band.

Ich möchte in meiner Anwendung die Google OR Tools verwenden, wofür ich wohl meine Anwendung auf x64 für den Compile umstellen muss.

Mit der Anwendung wollte ich aber auch Daten aus einer Excel-Tabelle per OleDbConnection auslesen. Das klappt aber mit einem Compile auf x64 wohl nicht mehr und beim Versuch Daten aus der Excel auszulesen kommt die Fehlermeldung:

Der 'Microsoft.ACE.OLEDB.12.0'-Provider ist nicht auf dem lokalen Computer registriert" beim Import einer Excel-Datei.

Ich habe Excel 2007 und auch eine Excel-Datei, die damit erstellt ist. Unter x86 bzw. 32-Bit klappt zwar das Einlesen der Excel-Tabelle ohne Probleme, aber die Google OR Tools wollen damit dann nicht mehr.

Wie bekomme ich denn diese Beiden Welten am besten verheiratet?

31.12.2020 - 13:55 Uhr

Hhm, ich hoffe, ich hab das dann nicht zu kompliziert gemacht. Wirkt irgendwie nicht so prickelnd bis jetzt:


private void dataGridView_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
	DataGridView dataGridView = sender as DataGridView;
	if (dataGridView != null && e.ColumnIndex >= 0 && e.RowIndex >= 0)
	{
		var comboBoxCell = dataGridView[e.ColumnIndex, e.RowIndex] as DataGridViewComboBoxCell;
		if (comboBoxCell != null)
		{
			var dt = comboBoxCell.DataSource as DataTable;
			if (dt != null)
			{
				var dtSelectedRows = dt.Select().Where(rowValue => rowValue.ItemArray[0].ToString() == comboBoxCell.Value.ToString()).CopyToDataTable();
				if (dtSelectedRows.Rows != null && dtSelectedRows.Rows.Count == 1)
				{
					dataGridView[1, e.RowIndex].Value = dtSelectedRows.Rows[0].ItemArray[1];
					dataGridView[2, e.RowIndex].Value = dtSelectedRows.Rows[0].ItemArray[2];
					dataGridView[3, e.RowIndex].Value = dtSelectedRows.Rows[0].ItemArray[3];
				}
			}
		}
	}
}

Was mich auch gerade noch etwas verwundert, dieses Ereignis kommt erst, nachdem ich von meiner Zelle mit der DropDownBox den Fokus auf eine andere Zelle setze und nicht schon, wenn ein Wert aus der DropDownBox selektiert wurde und diese damit dann zuklappt.

31.12.2020 - 10:56 Uhr

Hallo zusammen,

ich habe ein DataGridView mit 10 Spalten. Dann hab ich eine DataTable dt mit Werten bestehend aus 4 Spalten. Die Werte der ersten Spalte (Primary Key) werden in einer DataGridViewComboBoxCell im DataGridView zur Auswahl angeboten und sind wie unten per DataSource, DisplayMember und ValueMember angebunden.

Nachdem ein Wert aus DataGridViewComboBoxCell ausgewählt wurde, sollen in 3 anderen Spalten in meinem DataGridView die entsprechend damit verknüpften Werte aus der DataTable angezeigt werden.


DataGridViewComboBoxCell artikel = new DataGridViewComboBoxCell();
artikel.DataSource = dt;
artikel.DisplayMember = dt.Columns[0].ColumnName; // "Artikel";
artikel.ValueMember = dt.Columns[0].ColumnName; // "Artikel";

Wie muss ich denn die 3 anderen Spalten anbinden, damit der Filter-Mechanismus über die DataGridViewComboBoxCell richtig funktioniert?

31.12.2020 - 09:50 Uhr

Hallo Th69,

es handelt sich hier um eine Datei aus Exel 2007. Diese liegt schon im *.xlsx Format vor. Das eigentliche "Problem" ist aktuell fast nur die schöne Anzeige.
Ein riesen Freund war ich nie vom DataGridControl und es zeigt sich auch hier wieder, dass es auf diesem Wege sehr viel manuelles Nacharbeiten mit vielen Kleinigkeiten ist. Wenn man in Excel auf "Entf" drückt ist der Wert aus einer Zelle gelöscht. Solche Dinge muss man im DataGridControl alle manuell und händisch nachbauen. Genauso mit den Linien und Umrandungen überall. Auch die DataGridViewComboBoxCell ist so ein Ding, wenn die integriert ist. Da ist der Mechanismus so, dass man 2x klicken muss, damit das Menü aufspringt. Ebenso ist das Farbschema nicht natürlich in das weiß der anderen Zellen integriert, sondern muss auch manuell nachjustiert werden.

Wie gesagt, sehr viel Handarbeit, um nur das Grund-Look-and-Feel von Excel zu haben.
Die Daten hole ich mir in die ComboBox per OLE DB mittels Microsoft.ACE.OLEDB.12.0 und weise diese DataTable dann per DataSource der DataGridViewComboBoxCell mit einem DisplayMember und ValueMember zu.

31.12.2020 - 08:35 Uhr

Hallo zusammen,

ich habe schon ein paar Beiträge durchgelesen, bei denen beschrieben wurde, wie man eine Excel-Datei in einem Control innerhalb einer WinForms-Anwendung anzeigen können soll (z.B. mit dem WebBrowser oder anderen ActiveX).

Leider haben diese Varianten bei mir nicht so richtig funktioniert und entweder wurde die Datei immer extern geöffnet oder aber, die Microsoft Controls zur Anbindung standen gar nicht zur Auswahl, obwohl Excel 2007 installiert ist.

Mir ging es eigentlich darum, dass ich etwas vom DataGridView weg kommen kann. Beim DataGridView muss man wirklich immer sehr viel händisch zusammenprogrammieren, um eine Grundfunktionalität und Optik zu bekommen, die Excel schon von Haus aus gut macht.

Ich möchte im Grund einfach einen Ausschnitt aus meiner Excel-Tabelle anzeigen und darin Werte auslesen und setzen (ohne die Excel-Datei wieder zu speichern).

In der Excel-Tabelle gibt es aktuell ein DropDown-Menü bei dem Werte aus einem anderen Datenblatt eingeblendet werden. Diese Werte für die DropDown-Box wollte ich in der WinForms-Anwendung auch aus einer ganz anderen Excel-Tabelle auslesen und in dem angezeigten Ausschnitt in den DropDown-Boxen zur Verfügung haben.

Die Frage ist, worüber würde man das heutzutage (VS 2019) realisieren und gibt es noch Controls, mit denen man wirklich einen Teil der Excel-Tabelle einblenden kann, oder ist diese Funktionalität komplett von der Bildfläche verschwunden und ich muss es wirklich alles händisch über das DataGridView machen?