Laden...

Forenbeiträge von OXO Ingesamt 86 Beiträge

29.12.2020 - 19:47 Uhr

Ich hab es jetzt mit meiner eigenen Lösung doch aufgegeben.

Der Google OR Tools Solver kann mit einem "SCIP"-Solver in Windeseile lösen. Die Lösung kommt sogar deutlich schneller, als mit dem Excel-Solver. Evtl. hab ich bei Excel durch Setzen eines Hakens noch etwas suboptimal konfiguriert, wenn das hier so schnell geht.

Die Konfiguration ist als Neuling allerdings etwas gewöhnungsbedürftig, aber wenn man die mal raus hat und wie man Gleichungen bzw. Constraints für den Solver bastelt, dann macht er wirklich gute Arbeit.

Frage ist nur noch, ob diese OR Tools überhaupt auf meinem iPhone und mit Xamarin zum Laufen zu bringen sind... Hürden über Hürden. Ansonsten muss ich doch wieder auf meine manuell programmierte Variante gehen..

29.12.2020 - 12:08 Uhr

Beim Überfliegen der Artikel sehe ich das ehrlich gesagt auch so.

Im Grunde wäre es mir wichtig, dass ich eine Lösung auf eine kleine Handy-App bringe. Für mich gedacht, von daher kann ich mit leicht höheren Laufzeiten leben, aber natürlich auch nicht übermäßig, ansonsten wird es auch eine zähe angelegenheit.

Ist Dir zufällig ein guter frei verwendbarer Solver bekannt, den man für solche Berechnungen hernehmen könnte?

29.12.2020 - 11:40 Uhr

Tja, ich würds gerne probieren, deinen Code zu verbessern - etwa durch Backtracking.
Allein ich sehe ihn nicht...?

Hier mal der bisherige Code (natürlich noch nicht optimal aufbereitet). Ist erst mal über einen Button einer Form zum Anstarten und am Ende wird noch kurz die sortierte Liste ausgegeben:


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;

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

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        /// <summary>
        /// Creates a value matrix in ascending order with the calculated value for each Quantity
        /// from its minimum to its maximum quantity
        /// </summary>
        /// <param name="weights">Proportions on each Rucksack</param>
        /// <param name="minimum">Minimum Quantity for this Element</param>
        /// <param name="maximum">Maximum Quantity for this Element</param>
        /// <returns>An array containing the calculated values (weight * quantity) and the quantity itself (e.g. [{0.22, 0.01, 0.34, 1}, {0.44, 0.02, 0.68, 2}, ..])</returns>
        private double[,] CalculateValueMatrixAscending(double[] weights, int minimum, int maximum)
        {
            int numberOfValues = Math.Abs(maximum - minimum);
            double[,] valueMatrix = new double[numberOfValues + 1, 4]; // Rucksack 1, Rucksack 2, Rucksack 3, Quantity

            for (int quantity = minimum, index = 0; quantity <= maximum; quantity++, index++)
            {
                for (int j = 0; j < 3; j++)
                {
                    valueMatrix[index, j] = quantity * weights[j];
                }

                valueMatrix[index, 3] = quantity;       // Quantity for which the value was calculated
            }

            return valueMatrix;
        }

        /// <summary>
        /// Creates a value matrix in descending order with the calculated value for each Quantity
        /// from its minimum to its maximum quantity
        /// </summary>
        /// <param name="weights">Proportions on each Rucksack</param>
        /// <param name="minimum">Minimum Quantity for this Element</param>
        /// <param name="maximum">Maximum Quantity for this Element</param>
        /// <returns>An array containing the calculated values (weight * quantity) and the quantity itself (e.g. [{0.22, 0.01, 0.34, 1}, {0.44, 0.02, 0.68, 2}, ..])</returns>
        private double[,] CalculateValueMatrixDescending(double[] weights, int minimum, int maximum)
        {
            int numberOfValues = Math.Abs(maximum - minimum);
            double[,] valueMatrix = new double[numberOfValues + 1, 4]; // Rucksack 1, Rucksack 2, Rucksack 3, Quantity

            for (int quantity = maximum, index = 0; quantity >= minimum; quantity--, index++)
            {
                for (int j = 0; j < 3; j++)
                {
                    valueMatrix[index, j] = quantity * weights[j];
                }

                valueMatrix[index, 3] = quantity;       // Quantity for which the value was calculated
            }

            return valueMatrix;
        }

        private void CalculateOptimalQuantities(int recursionDepth, double[][,] elements, double sumElement1_CurrentRecursionDepth, double sumElement2_CurrentRecursionDepth, double sumElement3_CurrentRecursionDepth, RucksackRestrictions rucksackRestrictions, LimitedSortedList sortedListOptimaleQuantities, OptimalQuantities optimalQuantitiesOverRecursion)
        {
            // Stop recursion at the lowest level (last Matrix)
            if (recursionDepth > elements.GetUpperBound(0))
                return;

            double[,] elementCurrentRecursionDepth = elements[recursionDepth];
            int numRows = elementCurrentRecursionDepth.GetUpperBound(0);  // extra variable to have the opportunity to manipulate it

            for (int row = 0; row <= numRows; row++)
            {
                // Save the quantity over the entire recursion hierarchy to
                // have the "Optimal Quantities" to the corresponding error value later on
                optimalQuantitiesOverRecursion.OptimalElementQuantities[recursionDepth] = (int)elementCurrentRecursionDepth[row, 3];      // Quantity of current line in the Matrix

                // Create sum for Columns (each column is a Rucksack) for each Element Quantity
                // of the current Matrix Combination
                var sumRucksack1 = sumElement1_CurrentRecursionDepth + elementCurrentRecursionDepth[row, 0];        // Rucksack 1
                var sumRucksack2 = sumElement2_CurrentRecursionDepth + elementCurrentRecursionDepth[row, 1];        // Rucksack 2
                var sumRucksack3 = sumElement3_CurrentRecursionDepth + elementCurrentRecursionDepth[row, 2];        // Rucksack 3

                // Determine if allowed maximum capacity of a Rucksack is exceeded and end recursion
                // Don't proceed next row/quantity as it would be even higher (when ascending order is used)
                if (sumRucksack1 > rucksackRestrictions.CapacityRucksack1 || sumRucksack2 > rucksackRestrictions.CapacityRucksack2 || sumRucksack3 > rucksackRestrictions.CapacityRucksack3)
                    return;

                CalculateOptimalQuantities(recursionDepth + 1, elements, sumRucksack1, sumRucksack2, sumRucksack3, rucksackRestrictions, sortedListOptimaleQuantities, optimalQuantitiesOverRecursion);

                // We are at the bottom of the recursion and on our last element,
                // so we can create an ErrorValue as a Distance to the allowed Maximum capacity
                // over all Rucksacks
                if (recursionDepth == elements.GetUpperBound(0))
                {
                    // The lower the better and the better the rucksacks are filled
                    var errorValue = (rucksackRestrictions.CapacityRucksack1 - sumRucksack1) + (rucksackRestrictions.CapacityRucksack2 - sumRucksack2) + (rucksackRestrictions.CapacityRucksack3 - sumRucksack3);

                    // This would be an Over-Optimization of the ErrorValue and
                    // basically should occur. So we end the recursion in this case
                    if (errorValue < 0)
                        return;

                    //// For a descending case
                    //// We iterate from the highest Quantity to the lowest Quantity and
                    //// save a value for the first time. After that save the following
                    //// x values to just show these values to the user
                    //int newEndIndex = row + sortedListOptimalQuantities.Capacity;
                    //numRows = newEndIndex <= row ? newEndIndex : row;

                    // Here we save the optimal Quantities we've collected
                    // over the levels of the recursion and with the calculated
                    // error value on the bottom of the recursion.
                    // Do a shallow copy to have a unique reference of the OptimalQuantity
                    // to save.                    
                    OptimalQuantities optimaleMengeToSave = new OptimalQuantities(optimalQuantitiesOverRecursion.OptimalElementQuantities.Length);
                    for (int i = 0; i < optimalQuantitiesOverRecursion.OptimalElementQuantities.Length; i++)
                    {
                        optimaleMengeToSave.OptimalElementQuantities[i] = optimalQuantitiesOverRecursion.OptimalElementQuantities[i];
                    }
                    optimaleMengeToSave.ErrorValue = errorValue;
                    optimaleMengeToSave.SumRucksack1 = sumRucksack1;
                    optimaleMengeToSave.SumRucksack2 = sumRucksack2;
                    optimaleMengeToSave.SumRucksack3 = sumRucksack3;

                    sortedListOptimaleQuantities.Add(optimaleMengeToSave);
                }
            }
        }

        private void cmdStartCalculation_Click(object sender, EventArgs e)
        {
#if DEBUG
            Stopwatch timer = new Stopwatch();
            timer.Start();
#endif            
            double[] weights_Element1 = new double[3] { 0.21, 0.53, 0.14 };
            double[] weights_Element2 = new double[3] { 0.841, 0.011, 0.011 };
            double[] weights_Element3 = new double[3] { 0, 0, 0.93 };
            double[] weights_Element4 = new double[3] { 0.14, 0.067, 0.56 };
            double[] weights_Element5 = new double[3] { 0.122, 0.003, 0.04 };
            double[] weights_Element6 = new double[3] { 0.003, 0.006, 0.114 };
            double[] weights_Element7 = new double[3] { 0.004, 0.003, 0.104 };

            int[] boundaries_Element1 = new int[2] { 0, 30 };       // Original boundaries: 0 - 30
            int[] boundaries_Element2 = new int[2] { 1, 30 };       // Original boundaries: 1 - 30
            int[] boundaries_Element3 = new int[2] { 5, 5 };        // Original boundaries: 5 - 5
            int[] boundaries_Element4 = new int[2] { 20, 150 };     // Original boundaries: 20 - 150
            int[] boundaries_Element5 = new int[2] { 100, 500 };    // Original boundaries: 100 - 500
            int[] boundaries_Element6 = new int[2] { 50, 100 };     // Original boundaries: 50 - 100
            int[] boundaries_Element7 = new int[2] { 50, 150 };     // Original boundaries: 50 - 150

            var valueMatrix_Element1 = CalculateValueMatrixAscending(weights_Element1, boundaries_Element1[0], boundaries_Element1[1]);
            var valueMatrix_Element2 = CalculateValueMatrixAscending(weights_Element2, boundaries_Element2[0], boundaries_Element2[1]);
            var valueMatrix_Element3 = CalculateValueMatrixAscending(weights_Element3, boundaries_Element3[0], boundaries_Element3[1]);
            var valueMatrix_Element4 = CalculateValueMatrixAscending(weights_Element4, boundaries_Element4[0], boundaries_Element4[1]);
            var valueMatrix_Element5 = CalculateValueMatrixAscending(weights_Element5, boundaries_Element5[0], boundaries_Element5[1]);
            var valueMatrix_Element6 = CalculateValueMatrixAscending(weights_Element6, boundaries_Element6[0], boundaries_Element6[1]);
            var valueMatrix_Element7 = CalculateValueMatrixAscending(weights_Element7, boundaries_Element7[0], boundaries_Element7[1]);

            IList<double[,]> elements = new List<double[,]>();
            elements.Add(valueMatrix_Element1);
            elements.Add(valueMatrix_Element2);
            elements.Add(valueMatrix_Element3);
            elements.Add(valueMatrix_Element4);
            elements.Add(valueMatrix_Element5);
            elements.Add(valueMatrix_Element6);
            //elements.Add(valueMatrix_Eleent7);


            // Maybe implement a check here, if there could be a calculation at all (e.g. if in ascending order the minimum is already an overflow)
            // ...


            LimitedSortedList limitedSortedListOptimalQuantities = new LimitedSortedList(5);
            RucksackRestrictions rucksackRestrictions = new RucksackRestrictions(40, 20, 45);
            var elementArray = elements.ToArray();

            // Create all Matrix-Combinations line by line and do the calculations
            var firstElement = elements[0];
            for (int i = 0; i <= firstElement.GetUpperBound(0); i++)
            {
                OptimalQuantities optimalQuantities = new OptimalQuantities(elements.Count);
                optimalQuantities.OptimalElementQuantities[0] = (int)firstElement[i, 3];

                double sumElement1_CurrentRecursionDepth = firstElement[i, 0];
                double sumElement2_CurrentRecursionDepth = firstElement[i, 1];
                double sumElement3_CurrentRecursionDepth = firstElement[i, 2];

                CalculateOptimalQuantities(1, elementArray, sumElement1_CurrentRecursionDepth, sumElement2_CurrentRecursionDepth, sumElement3_CurrentRecursionDepth, rucksackRestrictions, limitedSortedListOptimalQuantities, optimalQuantities);
            }

#if DEBUG
            timer.Stop();
            Debug.WriteLine($"Time Taken for Calculation: {timer.Elapsed.Minutes}m {timer.Elapsed.Seconds}s {timer.Elapsed.Milliseconds}ms");
#endif      

            // Just a simple output in Debug Console
            foreach (var optimalQuantity in limitedSortedListOptimalQuantities)
            {
                Debug.WriteLine($"{optimalQuantity}");
            }
        }
    }
}


using System.Collections;
using System.Collections.Generic;

namespace SolverAlgorithm
{
    class LimitedSortedList : IList<OptimalQuantities>
    {
        private IList<OptimalQuantities> limitedList = new List<OptimalQuantities>();

        public LimitedSortedList(int capacity)
        {
            this.Capacity = capacity;
        }



        public int Capacity { get; private set; }

        public OptimalQuantities this[int index] { get => limitedList[index]; set => throw new System.NotImplementedException(); }

        public int Count => limitedList.Count;

        public bool IsReadOnly => limitedList.IsReadOnly;

        public void Add(OptimalQuantities item)
        {
            int positionToInsert = limitedList.Count;
            for (int i = 0; i < limitedList.Count; i++)
            {
                if (item.CompareTo(limitedList[i]) < 0)
                {
                    positionToInsert = i;
                    break;
                }
            }

            limitedList.Insert(positionToInsert, item);

            if (limitedList.Count > this.Capacity)
                limitedList.RemoveAt(limitedList.Count - 1);
        }

        public void Clear()
        {
            limitedList.Clear();
        }

        public bool Contains(OptimalQuantities item)
        {
            return limitedList.Contains(item);
        }

        public void CopyTo(OptimalQuantities[] array, int arrayIndex)
        {
            limitedList.CopyTo(array, arrayIndex);
        }

        public IEnumerator<OptimalQuantities> GetEnumerator()
        {
            return limitedList.GetEnumerator();
        }

        public int IndexOf(OptimalQuantities item)
        {
            return limitedList.IndexOf(item);
        }

        public void Insert(int index, OptimalQuantities item)
        {
            throw new System.NotImplementedException();
        }

        public bool Remove(OptimalQuantities item)
        {
            return limitedList.Remove(item);
        }

        public void RemoveAt(int index)
        {
            limitedList.RemoveAt(index);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return limitedList.GetEnumerator();
        }
    }
}


using System;

namespace SolverAlgorithm
{
    class OptimalQuantities : IEquatable<OptimalQuantities>, IComparable<OptimalQuantities>
    {
        readonly int[] optimalElementQuantities;

        public OptimalQuantities(int numProducts)
        {
            this.optimalElementQuantities = new int[numProducts];
        }
        
        /// <summary>
        /// Optimal Quantities from first Element to last Element that has to be used
        /// </summary>
        public int[] OptimalElementQuantities { get => this.optimalElementQuantities; }
        public double ErrorValue { get; set; }

        public double SumRucksack1 { get; set; }
        public double SumRucksack2 { get; set; }
        public double SumRucksack3 { get; set; }


        public override string ToString()
        {
            return $"ErrorValue: {ErrorValue}; Sum Rucksack 1: {SumRucksack1}, Sum Rucksack 2: {SumRucksack2}, Sum Rucksack 3: {SumRucksack3}";
        }

        public int CompareTo(OptimalQuantities other)
        {
            // A null value means that this object is greater.
            if (other == null)
                return 1;
            else
                return this.ErrorValue.CompareTo(other.ErrorValue);
        }

        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }

        public override bool Equals(object obj)
        {
            if (obj == null) return false;
            OptimalQuantities objAsOptimaleMenge = obj as OptimalQuantities;
            if (objAsOptimaleMenge == null) return false;
            else return Equals(objAsOptimaleMenge);
        }        

        public bool Equals(OptimalQuantities other)
        {
            if (other == null)
                return false;

            return  (other.ErrorValue == this.ErrorValue) && 
                    (other.SumRucksack1 == this.SumRucksack1) && 
                    (other.SumRucksack2 == this.SumRucksack2) && 
                    (other.SumRucksack3 == this.SumRucksack3);
        }
    }
}


namespace SolverAlgorithm
{
    class RucksackRestrictions
    {
        public int CapacityRucksack1 { get; }
        public int CapacityRucksack2 { get; }
        public int CapacityRucksack3 { get; }

        public RucksackRestrictions(int capacityRucksack1, int capacityRucksack2, int capacityRucksack3)
        {
            this.CapacityRucksack1 = capacityRucksack1;
            this.CapacityRucksack2 = capacityRucksack2;
            this.CapacityRucksack3 = capacityRucksack3;
        }
    }
}

Den Versuch/Ansatz mit Partitionierung der Matrizen, ähnlich binärer Suche, hab ich mal raus gelassen.

Zumindest über die Schleife für die erste Matrix wollte ich gerne eine Parallel.For(...) probieren. Kannst aber ja vergessen mit der LimitedSortedList.

Ohne diese Spaßbremse "LimitedSortedList" läuft sehr schnell auch der Speicher voll. Da kannst dem Untergang zusehen.

Wie gesagt, ich wollte das gerne auch noch auf mein Handy bringen, so dass Performance und Speicher schon auch echte Beschränkungen sind.

28.12.2020 - 19:58 Uhr

Rekursiv alle Kombinationen durchgehen und dabei die (bisher) beste merken.
Bei der rekursiven Vertiefung immer abbrechen, wenn der aktuelle Wert bereits schlechter ist als der bislang gefundene beste Wert ?

Also bei meinen Input-Daten mit 6 Elementen läuft die Berechnung mittels Rekursion in etwa 40 - 45s durch.

 
            double[] weightsElement1 = new double[3] { 0.21, 0.53, 0.14 };
            double[] weightsElement2 = new double[3] { 0.841, 0.011, 0.011 };
            double[] weightsElement3 = new double[3] { 0, 0, 0.93 };
            double[] weightsElement4 = new double[3] { 0.14, 0.067, 0.56 };
            double[] weightsElement5 = new double[3] { 0.122, 0.003, 0.04 };
            double[] weightsElement6 = new double[3] { 0.003, 0.006, 0.114 };
            //double[] weightsElement7 = new double[3] { 0.004, 0.003, 0.104 };

            int[] boundariesElement1 = new int[2] { 0, 30 };
            int[] boundariesElement2 = new int[2] { 1, 30 };
            int[] boundariesElement3 = new int[2] { 5, 5 };
            int[] boundariesElement4 = new int[2] { 20, 150 };
            int[] boundariesElement5 = new int[2] { 100, 500 };
            int[] boundariesElement6 = new int[2] { 50, 100 };
            //int[] boundariesElement7 = new int[2] { 50, 150 };

           ...

Ich möchte evtl. später daraus noch ne kleine App mittels Xamarin machen, so dass man das irgendwie auch auf nem iPhone zum Laufen bekommt. Da ich plane, vielleicht bis zu 8 Elemente zur Konfiguration zu erlauben, sollte ich schon nochmal was in Sachen Performance verbessern.
Excel bekommt das bei größeren Mengen mit seinem Solver effizienter hin.

Wenn ich wüsste, wie ich das Speichern in meiner LimitedSortedList parallel richtig verwalten kann, könnte ich eine Variante mit Parallel.For(...) oder ähnlichem noch probieren. Bei mir wird das ja aktuell auf einem Core nur gemacht, denke ich mal und der Flaschenhals ist tatsächlich dann das Einfügen in die LimitedSortedList. Lasse ich die Sortierung und Begrenzung weg, dann bekomme ich schnell eben den Speicher voll und es raucht auch ab. Für ein Handy ist das ganze damit eh so gestorben.

Ich weiß nicht, wasn man am Handy an Wartezeit für so eine Berechnung akzeptieren kann, aber das muss doch auch irgendwie noch besser gehen. Der Excel-Solver bekommt das doch auch besser hin.

28.12.2020 - 18:54 Uhr

Rekursiv alle Kombinationen durchgehen und dabei die (bisher) beste merken.
Bei der rekursiven Vertiefung immer abbrechen, wenn der aktuelle Wert bereits schlechter ist als der bislang gefundene beste Wert ?

Das Ding ist, man kann nur bei der Verknüpfung an der letzten Matrix den Fehlerwert genau bestimmen. Klar, wenn auf dem Weg da hin schon irgendwie der erlaubte Rucksack überlaufen ist, dann kann man abbrechen, aber so viel bringt das irgendwie nicht.

Beim Rucksackproblem mit mehreren Rucksäcken verteilt man die n Elemente optimal auf die verschiedenen Rucksäcke.

Bei mir ist es so, dass jedes Element rein muss und dabei einen gewissen Einfluß auf alle 3 Rucksäcke hat. Die genaue Menge jedes Elementes ist halt noch nicht bekannt.

28.12.2020 - 18:23 Uhr

Wenn ich für jedes Element schon mal die gewichteten Mengen zwischen der Untergrenze und der Obergrenze berechnet, geht das schnell. Damit hab ich Matrizen für jedes Element. Die sehen dann in etwa so aus:

Element 1
Mögliche Mengen: 1..30
Gewichte für jeden Rucksack: { 0.21, 0.84, 0 }
Matrix gewichteter Mengen:


{ 1  0.21  0.84  0,
  2  0.42  1.68  0,
  ...
  30 6.30  25.20 0
}

So eine Matrix für jedes Element 1..n wird als Input genommen.

Danach dann Berechnung aller Kombinationen/Permutationen aller Zeilen aller Matrizen!
So komme ich dann auch an die Spaltensummen für jede Kombination der Elemente 1..n und kann den Fehlerwert berechnen.

Wenn die Spaltensumme mal aus dem Ruder läuft, brauche ich in dieser Matrix nicht noch 1 Element weiter gehen, da ja dann der Fehler noch größer wird.

Also das ist aktuell so fast noch die einzige Optimierung, die mir einfällt. Ansonsten wüsste ich gerade nicht.

Ein Gedanke war wie gesagt, ob man nicht Partitionieren könnte und die Matrizen per Binärer Suche in die Hälfte teilen, schauen, ob das Ergebnis bei einer Zeile davor oder danach besser wird und dann weiter in diese Richtung suchen.

Ich befürchte nur, so richtig kann ich das damit gar nicht bewältigen, denn es könnte ja durchaus sein, dass ich bei einer der ersten Matrizen feststelle, ich sollte besser nach oben gehen, dann wird aber das Ergebnis aufgrund der errechneten gewichteten Werte für den anderen Topf irgendwo schlechter und ich müsste besser da nach unten gehen, damit es mit den anderen Matrizen und Rucksäcken noch besser wird.

28.12.2020 - 15:15 Uhr

Ja, allerdings so ganz konnte ich das noch nicht durchdringen, wie ich das basteln soll, wenn ich ehrlich bin.

Hier sind es 3 Rucksäcke, wobei jedes Element (jeder Gegenstand) immer alle 3 Rucksäcke beeinflusst. Dazu hat jedes Element eine Gewichtung für jeden Rucksack, die dann mit der errechneten Menge multipliziert werden muss.

Gewichtung Element 1 = { 0.21, 0.84, 0 }
Gewichtung Element 2 = { 0.133, 0.56, 0.31 }
..
Gewichtung Element n = { 0.011, 0.024, 0.5 }

Mögliche Mengen Element 1: 0 .. 30
Mögliche Mengen Element 2: 1 .. 30
Mögliche Mengen Element 3: 100 ... 500
...
Mögliche Mengen Element n: 50 .. 150

Kapazität Rucksack 1: 40
Kapazität Rucksack 2: 20
Kapazität Rucksack 3: 45

Gefunden werden muss für jedes Element die optimale Menge zwischen den Grenzen, um jeden Rucksack möglichst voll zu packen. Jedes Element hat eine untere und obere Schranke zwischen der sich die Menge bewegen dürfen.

On Top: zweige 5 besten 5 Lösungen an.

Vielleicht hab ich das aktuell blöd umgesetzt, aber zumindest komme ich so zu meinem Ergebnis. Von der Laufzeit her ist es nicht so toll, wenn mehr Elemente hinzu kommen.

Bin dazu hergegangen und hab für jedes Element 1 .. n eine eigene Matrix erstellt, in der in jeder Zeile ein Wert für jeden Rucksack drin steht, der die Menge * Gewicht enthält. Jede Zeile also von der Untergrenze bis zur Obergrenze. Für Element 1 sind das zum Beispiel 31 Zeilen für die möglichen Mengen 0 .. 30. Also jede Zeile hat 3 Spalten mit Menge * Gewicht für jeden Rucksack.

Danach bilde ich alle Kombinationsmöglichkeiten in einer Rekursion. In dem Zuge Bilde ich gleich die Spalten-Summen, die ja den Füllgrad des jeweiligen Rucksacks bilden. Ich checke da auch, ob noch eine höhere Menge in den Rucksack passt.

Um zu prüfen, welche der möglichen Lösungen die beste ist, hab ich einen Fehlerwert errechnet, der den Abstand/Fehler zum Limit der Rucksäcke ergibt:

Fehler = (Kapazität Rucksack 1 - Summe Spalte 1) + (Kapazität Rucksack 2 - Summe Spalte 2) + (Kapazität Rucksack 3 - Summe Spalte 3)

Wenn man alle Kombinationen der Matrizen gebildet hat, kann man nach dem Minimum der Fehlerwerte suchen. Der kleineste Fehlerwert ist dann die optimalste Füllmenge, um alle Rucksäcke möglichst voll zu machen.

So viel zur Theorie. Nur, in der Praxis ist das Ausrechnen aller Kombinationen natürlich teuer und nicht so der Hit was die Performance angeht.

Hatte schon überlegt, ob ich für jede Matrix-Kombination per binärer Suche anfangen soll, die Elemente davor und danach prüfen und ob der Fehlerwert dabei kleiner oder größer wird, um dann die Suche auf den optimalere Teilbereich weiter einzugrenzen. Da hab ich mich aber glaub etwas verrannt, denn es kann durchaussein, dass man bei einem Elment eine geringere Menge nehmen sollte, dafür bei einem anderen eine höhere, damit das Ergebnis optimaler wird.

Ob sich mit dieser binären Variante so überhaupt das Problem errechnen lässts, weiß ich noch nicht.

Eine Implementierung für 3 Rucksäcke und den Randbedingungen, wie oben beschrieben, ist mir bisher noch nicht gelungen, außer der über Berechnung aller Kombinationen, was aber von der Laufzeit her, nicht so toll ist.

28.12.2020 - 13:40 Uhr

Puh, also dann sprenge ich mal den Rahmen hier:

Die Aufgabe ist ein Optimierungs-Problem, bei dem die x besten Lösungen angezeigt werden können sollen.

Ich habe 3 Töpfe, die jeweils ein vorgegebenes Budget haben, z.B. 40 / 20 / 45
Dann gibt es n verschiedene Elemente, die frei ausgewählt werden können und Mengen von x bis y haben können. Zu jedem Element gibt es einen Vektor v, der den jeweiligen Anteil an den 3 Töpfen bei der Menge 1 beinhaltet.

Der Algorithmus soll nun die optimalen Mengen für jedes der n Element finden und zwar so, dass diese Daten im Vektor multipliziert mit der Menge am Ende die 3 Töpfe möglichst optimal ausfüllt. Natürlich darf kein Topf überlaufen. Am Ende könnten folgende Mengen als optimal raus kommen z.B. Element 1: 10, Elment 2: 12, ... Element n: 3

Ich berechne Kombinationsmöglichkeiten mit den verschiedenen Mengen und zu jeder einen Fehlerwert, der praktisch der Abstand zur optimalen Befüllung aller Töpfe ist. Je kleiner der Abstand, desto besser ist diese Mengen-Kombination (also z.B. Element 1: 10, Elment 2: 12, ... Element n: 3 -> ergibt Fehlerwert 0.12)

Die Errechneten Fehlerwerte mit den jeweiligen Mengen, werden dann in meiner LimitedSortedList gespeichert. Die Liste soll z.B. auf 5 Einträge beschränkt sein und nur die Mengen anzeigen, die den kleinsten Fehlerwert haben. Sprich, für die Anzeige, die 5 besten Mengen-Kombinationen.

Die Experimente mit der Parallelität sind gerade etwas aus Verzweiflung. Als Input hab ich mal angefangen für jedes Element 1 ... n eine Matrix mit den Werten zu erstellen, die von der Mindestmenge bis zur Höchstmenge eines Elementes läuft (bei jedem Elemente 1 .. n kann man noch definieren, was die Mindestmenge und was die Höchstmenge sein soll).
Dann hab ich mal gestartet und jede Kombinationsmöglichkeit der Matrizen ausgerechnet, dazu den Fehlerwert und dabei schon mal drauf geachtet, dass ich an der Höchstgrenze der Töpfe halt mache.

Trotzdem purzeln logischerweise sehr sehr viele Werte dabei raus und ich will aber ja nur 5 davon haben mit dem besten Fehlerwerten. Parallelität deshalb, weil logischweise so eine Berechnung davon profitieren kann. Schlecht, weil meine LimitedSortedList so natürlich nicht zurecht kommt.

28.12.2020 - 10:48 Uhr

Wenn du in der Liste sowieso nur die x kleinsten Werte haben willst, warum fügst du dann größere Werte überhaupt hinzu?

Ich weiß doch gar nicht, welche Werte sich darin befinden !?!?

28.12.2020 - 10:35 Uhr

Wenn man auf Performance abzielt könnte man beim Einfügen sogar BinarySearch zum Auffinden der Einfüge-Position anwenden. Dazu einfach die innere Liste nehmen - das ist eine List<T>, und List<T> kann BinarySearch.
Dann muss beim Add nicht mehr sortiert werden. Nicht viel Aufwand, aber bei 10 Elementen scheint mir auch das unsinnig.

Wie würde man mit BinarySearch eine Einfüge-Position ermitteln können? Ich dachte, so finde ich nur die Position eines vorhandenen Elementes.

In der Zwischenzeit blockiert mich tatsächlich aber auch die Parallelität, wie

schon erwähnt hatte.

Ich habe etwas am übergeordneten Algorithmus mit Parallel.For(...) experimentiert und wie schon erwähnt wurde, ist ein exklusives Einfügen hier nun mein Flaschenhals.

Am Einfachsten wäre es natürlich, wenn ich all meine berechneten Ergebnisse einfach mit nem simplen Add(...) der Liste hinzufügen könnte. Aber da läuft mir dann der Speicher über, da es einfach eine zu große Datenmenge ist.

Heißt, ich müsste ab und an schauen, dass ich eine Sortier-Aktion mit Dezimierung auf x Elemente einbaue, aber parallel ist das auch schon wieder so ein Kraftakt, der nicht klappen will.

26.12.2020 - 13:02 Uhr

Warum sortierst und limitierst nicht nur beim Anzeigen?

Hab nicht so weit ausgeholt, aber das Limitieren hat Gründe wegen dem Speicher.

Die Berechnungen ergeben so viele Daten, dass mein Programm irgendwann wegen nicht genügend Speicher weg krachen würde. Wollte mit dieser Liste dann das Problem mit der Limitierung umschiffen. Ansonsten würde es auch gehen.

26.12.2020 - 09:06 Uhr

Ist immer die Frage, was ist für den Anwender besser.

Die Frage ist immer noch, was Du eigentlich vor hast.
Siehe mein Beitrag oben, den Du offenbar etwas ignoriert hast 😉

Tut mir leid, das war keine Absicht.

Im Grunde ist es nur, dass man eine Liste hat, die auf x Elemente begrenzt ist und bei der neue Elemente nur aufgenommen werden, wenn sie nicht größer sind, als das größte Element.

Es werden irgendwo im Programm Fehlerwerte berechnet und diese sollen natürlich möglichst gering sein. Angezeigt haben, möchte ich die besten x Ergebnisse mit den geringsten Fehlerwerten.

Die Sortierung dieser LimitedSortedList ist hier an sich nur Mittel zum Zweck, um zu sehen, welches Element raus fliegt, um Platz für einen noch kleineren Wert zu machen.

25.12.2020 - 07:53 Uhr

Hallo T-Virus,

zunächst einmal vielen Dank für Deine Antwort und die Mühe!

Deine Frage kann man immer mit Ja beantworten, kannst du so implementieren.
Die Umsetzung ist aber, wie Abt auch schon schreibt, dann nur teuer durch die Sortierung bei jedem Insert.
Ebenfalls solltest du dann auch beachten, wenn AddRange verwendet wird, dass du dort dann

Was wolltest Du hier noch schreiben?

Ebenfalls hast du durch hinzufügen über die Kapazität hinaus dann auch das Problem, dass die List<T> beim ersten Überschreiten der vorgegebenen Größe dann das interne Array vergrößern + umkopieren muss.

Aktuell hab ich die maximale Kapazität erst einmal nur über meine interne Grenze festgelegt und verwende die normale List-Implementierung intern.

Aktuell fehlt auch ein AddRange, was bei größeren Add Operationen z.B. durch eine andere Liste dazu führen würde dass du die Elemente einzeln hinzufügen und jedes mal die komplette Liste sortieren musst.
Dies wäre eine totaler Overkill an Rechenlast bei großen Listen.

Da hast Du Recht! An ein AddRange hab ich zunächst einmal gar nicht gedacht und könnte ich noch einbauen

Nachtrag:
Bei der aktuellen Implementierung zeigen sich auch schon erste Schwächen der Implementierung.

1.Warum soll der [] Index Operator bei Get und Set eine Exception werfen?
Dadurch kannst du keine gezielten Elemente abfragen, was aber nötig wäre wenn du z.B. auf ein bestimmtes Element zugreifen willst!
Hier würde ich direkt an den Index Operator der Liste zugreifen für Get.
Bei Set hingegen würde ich entweder eine Exception werden oder den setter leer Implenentieren, also ohne Funktion.

Das war eigentlich blöd, da ich das nur mal so runter implementiert hab. Da gebe ich Dir vollkommen Recht auch, das muss ich nochmal nacharbeiten. Denn diese Funktionalität muss schon her

Was wäre beim Setter für den Anwender vorteilhafter? In der MSDN findet man oft auch Hinweise auf Exceptions, die bei der ein oder anderen Funktion geworfen werden. Ein richtiger Freund bin ich aber hier auch nicht davon. Ein Einfügen an einem bestimmten Index macht aber so natürlich auch nicht wirklich Sinn. Und ein "Einfügen am Index" mit einem Element, was wegen der Begrenzung auf x Elemente auch nie in der Liste ankommt, wenn es wegen der Sortierung raus fliegen würde, ist irgendwie auch komisch. Ansonsten hätte ich gesagt, gut, das Insert könnte auch über das normale Add(...) geschleust werden. Aber das ist wirklich seltsam als Anwender dann. Vermutlich dann doch noch das kleinste Übel mit der Exception

2.Ebenfalls wirft CopyTo eine Exception, was unnötig ist.
Die Daten kannst du per CopyTo ohne Probleme kopieren lassen, wenn es welche gibt.
Hier kannst du eigentlich die Methode deiner Liste aufrufen lassen.

Richtig, die könnte ich auch noch entsprechend implementieren

3.Bei Insert macht die Exception schon Sinn, könnte man aber auch als leere Operation implementieren wenn nur du diese Klasse verwendest.

Ist immer die Frage, was ist für den Anwender besser. Meine Vorgehensweise ist meistens, dass ich versuche so wenig wie möglich Exceptions zu werfen (was man mit meiner obigen Implementierung nicht meinen würde). Vielleicht eine leere Implementierung doch irgendwie sympatischer.

24.12.2020 - 14:27 Uhr

Die Liste soll Elemente vom Typ MyData verwalten und auf x Elemente begrenzt sein.

Welche Elemente in die Liste kommen, ist von dem Wert der MyData.ErrorValue-Property abhängig (nach der wird dem wird die Liste sortiert). Das ständige Sortieren ist praktisch nur Mittel zum Zweck, um zu erkennen, welche Elemente noch in der Liste verbleiben dürfen.


class MyData : IEquatable<MyData>, IComparable<MyData>
{
     readonly int[] optimalRatios;

     public MyData(int numOptimalRatios)
     {
         this.optimalRatios= new int[numOptimalRatios];
     }
        
     public int[] OptimalRatios { get => this.optimalRatios; }
     public double ErrorValue { get; set; }

     public double Value1 { get; set; }
     public double Value2 { get; set; }
     public double Value3 { get; set; }

     public override string ToString()
     {
         return $"ErrorValue: {ErrorValue}; Value 1: {Value1}, Value 2: {Value2}, Value 3: {Value3}";
     }

     public int CompareTo(MyData other)
     {
         // A null value means that this object is greater.
         if (other == null)
             return 1;
          else
              return this.ErrorValue.CompareTo(other.ErrorValue);
     }

     public override int GetHashCode()
     {
         return this.ToString().GetHashCode();
     }

     public override bool Equals(object obj)
     {
         if (obj == null)
             return false;

         MyData objAsMyData = obj as MyData;
         if (objAsMyData == null)
             return false;
         
         return Equals(objAsMyData);
     }

     public bool Equals(MyData other)
     {
         if (other == null)
             return false;

         return (other.ErrorValue == this.ErrorValue) && 
                (other.Value1 == this.Value1) && 
                (other.Value2 == this.Value2) && 
                (other.Value3 == this.Value3);
     }
}


class LimitedSortedList : IList<MyData>
{
	private readonly IList<MyData> limitedList = new List<MyData>();
	
	public LimitedSortedList(int capacity)
	{
		this.Capacity = capacity;
	}



	public int Capacity { get; private set; }

	public MyData this[int index] { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }

	public int Count => limitedList.Count;

	public bool IsReadOnly => limitedList.IsReadOnly;

	public void Add(MyData item)
	{
		int positionToInsert = limitedList.Count;
		for (int i = 0; i < limitedList.Count; i++)
		{
			if (item.CompareTo(limitedList[i]) < 0)
			{
				positionToInsert = i;
				break;
			}
		}

		limitedList.Insert(positionToInsert, item);
			
		if (limitedList.Count > this.Capacity)
			limitedList.RemoveAt(limitedList.Count - 1);
	}

	public void Clear()
	{
		limitedList.Clear();
	}

	public bool Contains(MyData item)
	{
		return limitedList.Contains(item);
	}

	public void CopyTo(MyData[] array, int arrayIndex)
	{
		throw new System.NotImplementedException();
	}

	public IEnumerator<MyData> GetEnumerator()
	{
		return limitedList.GetEnumerator();
	}

	public int IndexOf(MyData item)
	{
		return limitedList.IndexOf(item);
	}

	public void Insert(int index, MyData item)
	{
		throw new System.NotImplementedException();
	}

	public bool Remove(MyData item)
	{
		return limitedList.Remove(item);
	}

	public void RemoveAt(int index)
	{
		limitedList.RemoveAt(index);
	}

	IEnumerator IEnumerable.GetEnumerator()
	{
		return limitedList.GetEnumerator();
	}
}

Also z.B. Liste begrenzt auf 5 MyData-Elemente, deren ErrorValue mit Sortierung wie folgt ist {1.2, 5.3, 6.2, 8.1, 9.3 }

Ein neues MyData-Element mit ErrorValue = 5.3 in die Liste. In diesem Fall würde das Element mit dem Wert 9.3 raus fliegen {1.2, 5.3, 5.3, 6.2, 8.1 }

Meinst Du, dass eine Implementierung nach InsertSort mit nur 1 Element von der Performance her vorteilhafter ist, als wenn man es simpel macht und in der Add-Methode immer erst einmal das Element hinzufügt, dann sortiert über List<T>.Sort(...) und dann das letzte Element löscht, wenn die maximal erlaubte Kapazität überschritten ist?

Könnte man das wie oben mit der Daten-Klasse und Equals und GetHashCode auch so machen?

24.12.2020 - 08:30 Uhr

Hallo Leute,

versuche gerade eine eigene Listen-Implementierung einer sortierten und limitierten Liste zu machen. Habe versucht, von einer List<T> abzuleiten. Da kann ich aber die Add-Methode gar nicht überschreiben, sondern nur überlagern mit new Add(T item). Das Interface IList<T> mit allen Implementierungen zu implementieren scheint mir auch komisch und too much.

Im Grunde wollte ich in einer Add(T item)-Methode eine Prüfung implementieren, die prüft, wo ein T sortiert einzufügen ist, damit die Liste immer sortiert bleibt. Gleichzeitig wollte ich darin darauf achten, dass die Liste eine initial festgelegte Kapazität nicht überschreitet (z.B. Begrenzung auf 10 Elemente).

Über die Collection<T> wollte ich nicht gehen, da ich hier nur eine Insert-Methode mit zusätzlichem Index überschreiben kann. Eine ArrayList<T>-Ableitung funktioniert auch nicht und bei einer ArrayList-Ableitung bietet es mir nur eine Implementierung von Add(object value) zum Überschreiben an, also nicht Add(T item), was für den Anwender der Liste schon schön wäre.

Wie muss ich das denn richtig machen, um bei meiner Listen-Implementierung wirklich am Ende etwas, wie Add(T item) zur Verfügung zu haben und warum geht das denn nicht über eine List<T>-Ableitung?

21.12.2020 - 17:28 Uhr

Mittlerweile ist die Geschwindigkeit ein nebensächliches Problem.

Ich hab keine vorgegebene Anzahl an Artikeln, sondern es können theoretisch beliebig viele sein.
Hab mir im Voraus mal Matrizen (Arrays) aufgebaut, die erst einmal alle Multplikationen von der angegebenen Mindestmenge, bis zur angegebenen Höchstmenge mit den angegebenen Grundwerten pro Spalte machen, also etwa so

Artikel 1:
Mindestmenge: 1
Höchstmenge 30
Grundwerte: 0.22 ; 0.83 ; 0.42

Dann würde für diesen Input eine Matrix mit den berechneten Werten entstehen. Die letzte Spalte enthält dann die Menge selbst, mit der die Berechnung gemacht wurde.

1x0.22 1x0.83 1x0.42 1
2x0.22 2x0.83 2x0.42 2
..
30x0.22 30x0.83 30x0.42 31

Artikel 2:
Mindestmenge: 100
Höchstmenge 500
Grundwerte: 0.12 ; 0.53 ; 0.31

100x0.12 100x0.53 100x0.31 100
101x0.12 101x0.53 101x0.31 101
..
100x0.12 100x0.53 100x0.31 500

Das Ganze für n Artikel dann mit n Arrays dieser Form.

Im Anschluß muss ich ja irgendwie versuchen alle Kombinationsmöglichkeiten zu bilden, um die Spalten-Summen und den Fehler-Wert (Abweichung) zu berechnen.

Die maximalen Spalten-Summen sind ja vorgegeben. Nachdem für eine Mengen-Konfigurtion die Spalten-Summen errechnet wurden, kann dann der Fehler-Wert, sprich die Abweichung zur vorgegebenen maximal erlaubten Spalten-Summe berechnet werden.

Eine Mengen-Konfiguration wäre dann am Ende z.B.
Artikel 1: Menge 10
Artikel 2: Menge 7
..
Artikel n: Menge 100

Mit diesen Mengen würde man ja für jede der 3 Spalten pro Artikel eine Zeile bekommen und kann sich dann die Spalten-Summe errechnen und sehen, ob diese möglichst optimal erreicht wird. Ob diese möglichst optimal erreicht wird, erkennt man am Fehler-Wert, der sagt, wie arg die Spalten-Summen von den Vorgaben abweichen. Dieser muss dann also möglichst klein sein.

Hat man erst einmal alle Kombinationsmöglichkeiten erstellt, kann man den Wert mit dem kleinsten Fehlerwert nehmen und hat damit die Vorgaben möglichst optimal ausgereizt.

Also vom Algorithmus her, nehme Zeile 1..n der Matrix 1 (Artikel 1), kombiniere diese mit Zeile 1..n der Matrix 2 (Artikel 2) kombiniere diese mit Zeile 1..n der Matrix n (Artikel n), berechne die jeweilige Spalten-Summen und den Fehler-Wert mit der Abweichung zu den vorgegebenen maximal erlaubten Spalten-Summen.

Ich hab die Berechnung aller Kombinationen mal mit Rekursion gemacht, da ich ja bei beliebiger Anzahl Artikel fast nicht anders ran komme, oder? Bei jedem Absteigen hab ich mir die nächste Instanz in eine Art Decorator eingepackt, um unten dann optimal die Spalten-Summen für diese Konfiguration errechnen zu können. Auf dem Weg hab ich mir auch noch eingesammelt, mit welcher Mengen-Konfiguration über die verschiedenen Artikel ich es gerade zu tun habe.

Am Ende hätte ich damit eine riesen Matrix mit allen Mengen-Konfigurationen und dem Error-Value dazu. Der kleinste Error-Value ist dann die optimalste Mengen-Konfiguration.

Man kann sich leicht vorstellen, dass das erstens schon dauern kann das alles zu berechnen, wenn man schon mit 4 - 5 Artikel arbeitet. Zweitens hab ich das Problem, dass mir jetzt der Speicher übergelaufen ist, weil das mit den ganzen Werten doch über die 2 GB raus ging. Vermutlich auch ein Stück wegen der Rekursion, wobei ich mir nicht sicher bin, wie arg die da überhaupt rein spielt, oder ob das nicht nur der Speicher der Ziel-Matrix ist.

Im Grunde bräuchte ich ja auch nicht alle diese Kombinationen vorrätig halten und berechnen, aber wie bestimme ich sonst die Spalten-Summen einer bestimmten Konfiguration aus n Artikeln?

Hab Ihr da ein paar Ideen , wie ich das algorithmisch anders machen könnte, oder vermeiden könnte, dass mir auf diese Weise der Speicher volläuft?

18.12.2020 - 10:35 Uhr

Denke, an dem, wie man es effizient bzw. am besten aufbaut.

Die Artikelmenge, also die Zeilen ab Zeile 4 sind von der Anzahl her variabel. Es kann 1, 2, ... Artikel geben, die zu berücksichtigen sind.

Für jede Zeile bzw. für jeden Artikel müsste ich alle Werte zwischen der Mindestmenge und Höchstmenge "durchprobieren". Also sozusagen gibt es doch dann für jeden Artikel eine weitere Schachtelung, also eine innere Schleife.

Also irgendwie ergibt das ja für jeden Artikel bzw. für jede Mindest- und Höchstmenge eine weitere geschachtelte Schleife, die von der Mindest- bis zur Höchstmenge laufen kann. Praktisch für jeden weiteren Artikel einen "Berechnungspfad".

Das verwirrt mich gerade, wie man sowas effizient angehen und gestalten könnte.

18.12.2020 - 09:26 Uhr

Eigentlich geht es darum, die Mengen in Spalte G errechnen.
Das sind natürlich am Ende optimierte Mengen, so dass die Werte der Zeile 12 möglichst nahe an die gesetzten Werte der Zeile 13 ran kommen.

Die Spalten werden dabei jeweils mit der zu errechnenden Menge aus Spalte G multipliziert.

Als Beispiel ergibt sich in B12 das Ergebnis wie folgt:

B4G4 + B5G5 + B6G6 + B7G7 + ... + B10*G10

G12 errechnet sich wie folgt:
(B13-B12) + (C13-C12) + (D13-D12)

Bedeutet also, das ist die Abweichung vom "Optimum" und diese sollte möglicht gering werden und nahe 0 sein.

Die Spalten E und F (Mindest- und Höchstmenge) sind einfach nur die Grenzen zwischen denen sich die optimale Menge der zu errechnenden Spalte G bewegen darf.

16.12.2020 - 19:11 Uhr

Hallo,

ich habe mir in Excel ein kleine Tabelle gemacht, die mit Hilfe des Excel-Solvers optimale Mengen errechnet.

Dazu habe ich ein Schema, wie im angehängten Screenshot.

In Spalte A sind Artikel hinterlegt, die man auswählen kann. Zu jedem Artikel sind kleinere Werte W1 - W3 auf 100g hinterlegt (Spalten B - D). In den Spalten E und F kann man angeben, in welchem Bereich sich die Mengen bewegen müssen

In Zeile 12 gibt es ein Summenprodukt mit der Menge aus Spalte G. In Zeile 13 wird als Beschränkung angegeben, wie hoch dieses Summenprodukt maximal sein darf. Der Solver errechnet dann die optimalen Mengen in der Spalte G, um diese Summenprodukte möglichst hoch zu machen, damit die Werte der Zeile 13 möglichst optimal erreicht werden. Hierzu hat der Solver Beschränkungen, so dass die Werte über die Formel =(B13-B12)+(C13-C12)+(D13-D12) möglichst klein werden. Nennen wir das mal die Abweichung oder den Fehler, der dann möglichst klein sein soll.

Meine Frage: wie kann ich diese Berechnung mit C#-Bordmitteln machen? Das Problem ist, dass ich das Ganze später auf meinem Handy laufen lassen möchte, bei dem ich keinen Excel-Solver zur Verfügung habe und möglichst auch keine externe Bibliothek einbinden möchte. Wie erreiche ich denn die Berechnung dieser möglichst optimalen Mengen so?

27.06.2020 - 09:19 Uhr

Hallo zusammen,

ich finde den Resharper wirklich super. Allerdings muss ich auch zugeben, dass mir das für den privaten Gebrauch mit einer laufenden Jahresgebühr einfach zu teuer und dann mit laufenden Kosten irgendwie doch nicht Wert ist. Einen Einmal-Preis hätte ich mir vielleicht noch überlegt.

Frage: welche Alternativ-Tools mit paar der Funktionen von Resharper (z.B. auch, dass man aus einem geöffneten File auch direkt in den Project-Explorer zu dieser Stelle wechseln kann; natürlich die Standard-Sachen, wie Extract-Method etc.) könnt Ihr empfehlen? Eventuell sogar auch was im Freeware-Bereich?

26.06.2020 - 18:09 Uhr

Dachte ich mir schon, dass ich das noch nicht ganz richtig gemacht hab - Mist 😉
Bei Benutzung der Methode von oben ist es aber schwierig den sicheren Zeitpunkt für das Abräumen zu bestimmen.

Den Speicher für den pszTextPtrForeign kann ich z.B. in der Routine noch gar nicht frei geben, da der ja meinen Pointer im Prozess für den pszText das LV_ITEM repräsentiert. Genauso hab ich ein Problem mit dem allokierten Speicher, solange ich noch im Weiterleiten einer veränderten LVM_INSERTITEMW Message in meinem CallBack bin. Irgendwie darf ich das ja alles erst abräumen, wenn die komplette Nachricht am Ziel angekommen und verarbeitet ist.
Dazu ist im Hook-Callback eigentlich gar keine Möglichkeit.

Denke, zumindest das CloseHandle(...) auf den Prozess-Handle könnte ich noch in der Routine unterbringen, da dieser nach dem WriteProcessMemory(...) wohl nicht mehr gebraucht wird

Der Grund für das langsame Update des ListView nach dem Verändern meiner Hook-Message kann das aber nicht sein, oder könnte das eine Rolle spielen? Da kann man nämlich darauf warten, bis das ListView seine paar Einträge geladen hat.

26.06.2020 - 15:20 Uhr

Das sind gute Hinweise!

Werde ich mal so weitergeben. Wenn ich das so lesen, denke ich, dass es dann über File.WriteAllText(...) wohl am besten ist.

Vielen Dank schon einmal!

26.06.2020 - 09:40 Uhr

Hab mich jetzt mit dem Thema Hooking herum geschlagen und experimentiert.

Ist schon sehr aufwändig, aber ich hab es hinbekommen und es wird jetzt eine C++-DLL verwendet, mit der ich einen Hook setzen kann (z.B.

WH_CALLWNDPROC

). Von dort aus verzweigt es dann auch zurück in ein C# Progrogramm. Hier kann ich einige Messages sehen und bin auch soweit gekommen, dass ich eine

LVM_INSERTITEMW

verändere und den Text hierüber im Ziel-ListView des anderen Prozesses austausche.

Eine ganz interessante Erfahrung war, dass man Speicher für die Pointer zu den Strings immer im Ziel-Prozess allokieren muss und von der Managed-Welt dort hin rüber schieben muss. Eigentlich logisch, aber trotzdem gewöhnungsbedürftig. Ebenfalls, dass ich noch den lparam aus dem

CWPSTRUCT

wieder auspacken muss, um dann bei den Messages auf ein

LVM_INSERTITEMW

reagieren kann. Danach noch den lparam in ein

LV_ITEM

-Struct pressen. Also schon alles mit viel Aufwand verbunden.
Hattet Ihr mich nicht schon gewarnt? 😛

Beim Lesen bzw. auch beim Anlegen der Strings muss man immer eine size mit angeben. Da ist mir ehrlich gesagt bis jetzt noch nicht klar, wie viel ich denn hierfür reservieren muss bzw. woher ich z.B. beim Lesen weiß, wie viel ich reservieren soll? Genauso beim Schreiben/Anlegen der Strings hatte ich den Eindruck, ich muss auf meine str.Length noch eine Konstante oben drauf hauen muss, um eine größere size zu reservieren, damit es funktioniert.

Dann ist es so, dass man auch immer wieder mit

OpenProcess/VirtualAlloc/WriteMemory

arbeiten muss. Kann es sein, dass ich auch einen Gegenpart zum OpenProcess und dem VirtualAlloc brauche, um alles wieder freizugeben? Ich wundere mich nämlich, dass alles recht träge wirkt. Weiß aber auch nicht, ob das nicht einfach so ist?

Auch das Freigeben des pszTextPtrForeign wäre vermutlich noch sinnvoll, aber dann kann ich das ja nicht mehr erfolgreich zurückgeben, wenn ich den Speicher für diesen Pointer schon freigegeben hab.


public static IntPtr GetPtrToStringInProcessMemory(Process Process, string str)
{
	IntPtr ProcessHandle = OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_OPERATION, false, Process.Id);

	const int stringMaxAddedSize = 128;
	int maxStringSize = 0;

	if (String.IsNullOrEmpty(str)) // not initialized
		str = new string('x', 32);

	int size = str.Length + stringMaxAddedSize;
	if (size > maxStringSize)
		maxStringSize = size;

	IntPtr pszTextPtrForeign = VirtualAllocEx(ProcessHandle, IntPtr.Zero, (uint)size, AllocationType.Commit, MemoryProtection.ReadWrite);
	IntPtr TextPtrLocal = Marshal.StringToHGlobalAuto(str);

	bool Ok = WriteProcessMemory(ProcessHandle, pszTextPtrForeign, TextPtrLocal, size, IntPtr.Zero);
	if (!Ok) { throw new Win32Exception(Marshal.GetLastWin32Error()); }
	
	Marshal.FreeHGlobal(TextPtrLocal);

	return pszTextPtrForeign;
}

26.06.2020 - 09:04 Uhr

Hallo zusammen,

ein Kollege hat ein Tool zum Ersetzen von Zeichen aus Quellcode-Dateien geschrieben. Der ersetzte Text soll dann in eine Datei mit demselben Encoding geschrieben werden. Das Encoding wurde aus dem Input-File ermittelt und stimmt nach dem Schreiben des neuen Files mit dem Original überein.

Das Problem ist, dass der zu schreibende String zwar im Debugger richtig aussieht, aber dass nach dem Close() des StreamWriter die letzten 4 Zeilen aus dem String noch zusätzlich hinten angehängt werden. Also im Sinne eines Quellcode-Files, sind dann am Ende noch zusätzlich z.B. 4 Zeilen mit "}" zusätzlich zu sehen.

Wie ist sowas denn sowas zu erklären??

Ein zusätzlich expliziter Aufruf von Flush() und Close() hat bei dem Konstrukt, wie zu erwarten, auch nichts gebracht.


FileStream fs = null;
try
{
	fs = new FileStream(outputFile, FileMode.Open, FileAccess.Write);
	using (StreamWriter sw = new StreamWriter(fs, encoding))
	{
		sw.Write(content);
		// sw.Flush();
		// sw.Close();
	}
}
finally
{
	if (fs != null)
		fs.Dispose();
}

08.06.2020 - 23:10 Uhr

Mir sind auch noch ein paar interessante Links untergekommen. Zum einen mit Grundlagen für das Marshaling und auch noch mit Datentypen, die man häufig in den C/C++ Deklarationen in der MSDN zu den Funktionen findet und was für ein Managed-Datentyp das ist. Vielleicht interessant für das eigene Erstellen der Strukturen:

Interop-Marshalling

Standard-Marshalling Verhalten

Marshallen von Daten mit Platformaufruf

Zuordnen von HRESULT Werten und Ausnahmen

07.06.2020 - 21:07 Uhr

Für WinAPI ist es das einfachste, du installierst im VS zusätzlich C++ (und damit das
>
) - in den Headerdateien (u.a. <windows.h> oder z.B. <commctrl.h>) stehen dann die Deklarationen der Funktionen mit den Strukturen etc.

Was hältst Du davon, das gleich in einer C++ Datei in der eigenen C#-Anwendung zu realisieren? Oder macht es die Problematik dann von dieser zurück in die C#-Anwendung zu kommen nicht kleiner als direkt die Funktionen in C# anzusprechen und alle Typen zu recherchieren?

Ansonsten finden sich viele Funktionen und Strukturen auch bei
>
(wofür es auch ein VS Add-In gibt).

Und teilweise einfach bei konkreten Namen von Typen, Strukturen oder Konstanten/Enums im Internet danach suchen (leider fehlen die internen Werte von Konstanten/Enums in der offiziellen MS-Doku).

So habe ich das jetzt auch gemacht, aber das ist schon ne recht mühsame Recherche hatte ich den Eindruck. Irgendwie kenne ich die MSDN von früher noch, wo ich auch hin und wieder noch in VB vor .NET-Zeiten API-Calls gemacht hab. Da kam mir das in der MSDN transparenter vor und die Werte der Konstanten waren genauer beschrieben, so zumindest meine Erinnerung.

Nehmen wir gleich mal als Parade-Beispiel die SetWindowsHookExA-Beschreibung aus der MSDN. Da sind gleich mehrere Parameter, bei denen ich mich frag, wie ich die in die C#-Welt übertrage und richtig abbilde? HHOOK, HOOKPROC, HINSTANCE. Genauso bestimmte Pointer-Typen


HHOOK SetWindowsHookExA(
  int       idHook,
  HOOKPROC  lpfn,
  HINSTANCE hmod,
  DWORD     dwThreadId
);

Gibt es ne gute Einstiegsseite, bei der ich die generellen Grundprinzipien zum Umgang mit Pointern, Marshal etc. für Win API-Aufrufe erklärt sind?

07.06.2020 - 20:10 Uhr

Rein unter mit einer Aufwand <-> Nutzen Brille gebe ich Dir völlig Recht.
Allerdings sehe ich das auch unter dem Lern-Aspekt und das war ne interessante und teils lehrreiche Reise bis hierhin 😃

Vor allem die Erkenntnis, dass die Pointer ja irgendwie in fremdem Speicher liegen müssen und da die Dinge adressieren.

Vieles ist mir aber noch unklar, wie z.B. wo ich diese ganzen Werte für die Konstanten und auch die Strukturen für die API-Funktionen alle für C# nachschlagen kann? In der MSDN stehen die im C++ Code und teils mit Unterstrukturen und Pointern darauf. Mir ist immer noch nicht ganz klar, wo ich diese Informationen alle richtig für C# her bekomme und wie ich die richtig zusammenbasteln muss, damit die alle richtig versorgt sind.

07.06.2020 - 17:46 Uhr

Mein Vater nutzt "Windows-Fax- und -Scan" zum Scannen. Dort werden die Dateinamen für jeden neuen Scan erst einmal benannt mit "Bild". Ist diese Datei bereits vorhanden, wird ein neuer Scan "Bild (2)", "Bild (3)" etc. genannt. Löscht man z.B. "Bild (2)", wird für einen nächsten Scan diese Lücke gefüllt und es entsteht nicht "Bild (4)", sondern "Bild (2)".

Mein Vater möchte die Dateien gerne durchnummiert haben.

Ich habe ihm daher ein Tool mit einem FileSystemWatcher geschrieben, der diese neuen Dateien umbenennt, sobald sie in dem Verzeichnis nach dem Scannen erzeugt wurden.

Es gab bei ihm aber mehrere Umstände, die ich gerne aus Anwendersicht glatt gezogen hätte. So kamen nach und nach bei der Lösungsfindung neue fragen auf. Natürlich nicht nur bezogen auf dieses Problem, sondern auch allgemeiner Natur, weil ich mich zwischendurch gefragt habe, wie sowas gehen könnte.

Bei ihm war es so, dass z.B. der FileSystemWatcher immer 2x angesprungen ist, obwohl ich die Message in der CallBack-Funktion des OnCreated-Events auf "Created" abgefragt habe. Ich vermute, dass der Scanner-Treiber erst temporär eine Datei aufmacht und am Ende hab ich dann 2x das Event. Dann war die Feststellung, dass im ListView 2 Dateien auftauchen, die gescannte und meine neu benannte Datei. Nur, meine umbenannte Datei hatte die Information in der Spalte "Quelle" verloren. Diese stand als eine unbekannte Quelle bei der Datei im ListView. Die Original-Datei vom Scanner nach dem Scan, hatte diese Information aber dran. Dann wollte ich das zuerst von der alten Datei auslesen und in meine neue Datei übernehmen. Bin aber auch wieder davon abgekommen, da das ListView eh einen falschen Namen hatte.

Dann bin ich zwischenzeitlich zu dem Entschluss gekommen, dass ich mit meinem Umkopieren doch solange warte, bis der Scanner-Treiber seine Datei wirklich fertig erzeugt hat und ich erst dann umkopieren kann. Das hab ich dann gemacht, damit war aber das ListView nicht mehr Up-to-Date und hat die Datei so angezeigt, wie sie ursprünglich nach dem Scannen benannt war und nicht, wie ich sie neu genannt hab. Dafür stand aber die Information mit der Scanner-Quelle drin.

Somit war mein nächster Ansatz, ob ich nicht das ListView entsprechend umbenennen könnte nach der Umkopier-Aktion. Ich hatte gesehen, dass das ListView nicht neu initialisiert wird, egal was für Refresh-Aktionen ich da per API versucht hatte. Alles natürlich auch unter dem "Forschungsgedanken" wie macht man x oder y ganz generell. Da das mit dem ListView, wie Du aus den anderen Threads raus gelesen hast, etwas tricky war, kamen mehrere Überlegungen rein.

Zwischendrin dachte ich mir auch, gut vielleicht könnte ich mich auch einfach an die Nachrichten des ListView dran hängen und vor dem Einfügen den Eintrag manipulieren. Jetzt ließ sich das Problem ja anderweitig über das TreeView lösen, aber wie man die Nachrichten von anderen Controls abfängt und wie man sich rein hängen könnte, interessiert mich trotzdem noch, auch wenn das nicht als Lösung genommen wird 😃

07.06.2020 - 16:10 Uhr

Hallo zusammen,

ich hatte für die Lösung eines anderen Problems daran gedacht, ob es eine Möglichkeit wäre, Nachrichten anderer Steuerelemente einer fremden Anwendung abzufangen, zu manipulieren und dann an diese durchzulassen.

Konkret ging es darum, vor dem Einfügen eines Eintrags in ein ListView einer fremden Anwendung diesen vor dem Einfügen noch zu manipulieren.

Wollte daher nochmal in einem gesonderten Thread nachfragen, wie man Messages und MessageItems von Controls, die nicht in der eigenen Anwendung laufen, mithören kann? Den Handle zu dem Control habe ich in der Hand, aber wie abonniere ich mich auf dessen Nachrichten?

07.06.2020 - 14:09 Uhr

Du möchtest also die ListView-Anzeige aktualisieren. Leider unterstützt das Programm kein "Aktualisieren" (F5), wie man es vom Windows-Explorer oder anderen Programmen kennt, aber ein Reselektieren des TreeView-Eintrags müßte dazu ausreichen, s.
>
sowie
>
(mit TVGN_CARET als Parameter), d.h.

  
// Pseudocode  
IntPtr selectedItem = SendMessage(TVM_GETNEXTITEM, TVGN_CARET);  
SendMessage(TVM_SELECTITEM, IntPtr.Zero);  
SendMessage(TVM_SELECTITEM, selectedItem);  
  

Ja super, jetzt geht das, was ich machen wollte wohl. Als ich händisch Ordner selektiert hatte und zurück gegwechselt bin, wurde wohl erst alles aus dem Cache für das ListView bezogen.

So funktioniert das Anzeigen nach dem Umbenennen anscheinend, wenn ich die Selektion wie folgt weg nehme:


// Werden nicht alle gebraucht
const int TV_FIRST = 0x1100;
const int TVGN_ROOT = 0x0;
const int TVGN_NEXT = 0x1;
const int TVGN_CHILD = 0x4;
const int TVGN_FIRSTVISIBLE = 0x5;
const int TVGN_NEXTVISIBLE = 0x6;
const int TVGN_CARET = 0x9;
const int TVM_SELECTITEM = (TV_FIRST + 11);
const int TVM_GETNEXTITEM = (TV_FIRST + 10);
const int TVM_GETITEM = (TV_FIRST + 12);

string windowTitle = "Windows-Fax und -Scan";
IntPtr ptrWnd = FindWindow(null, windowTitle);
IntPtr ptrWndMDIWindow = FindWindowEx(ptrWnd, IntPtr.Zero, "AfxMDIFrame42u", null);
IntPtr ptrWndNestedMDIWindow1 = FindMDIWindowByIndex(ptrWndMDIWindow, 1);
IntPtr ptrWndFrameOrView = FindWindowEx(ptrWndNestedMDIWindow1, IntPtr.Zero, "AfxFrameOrView42u", null);
IntPtr ptrWndTreeView = FindWindowEx(ptrWndFrameOrView, IntPtr.Zero, "SysTreeView32", null);

// Remove Selection and select Root
var selectionRemovedResult = SendMessage(ptrWndTreeView, TVM_SELECTITEM, TVGN_CARET, 0);

07.06.2020 - 11:59 Uhr

Puh, da hast du dir aber was vorgenommen.

Ob man die A- oder W-Variante beim Marshalling nehmen muß, hängt von den zu benutzenden Funktionen der DLL (bzw. in deinem Fall: des anderen Prozesses ab). Moderne Programme sollten eigentlich UNICODE-fähig sein und damit sollte man die W-Variante benutzen.

Okay, vermutlich bei den meisten DLLs unter Windows 10 dann schon der Fall.

Beim Umgang mit der WinAPI fällt mir momentan eigentlich am Schwersten, die richtigen Strukturen und Datentypen, sowie deren Werte für die Konstanten für C# irgendwo her zu bekommen.

Wie gehst Du denn da genau vor? In der MSDN findet man ja meist die Structs für C++, teils mit irgendwelchen Unter-Strukturen und Elemente.

Die Frage ist für mich immer, wie bastel ich das für C# richtig hin?

Die simplen Datentypen sind ja kein Thema, aber bereits bei den Strings scheint man komische Pfade zu betreten, wie z.B. diese Geschichte, dass diese in einem anderen Prozess liegen müssen.

Vielleicht als kleine Lehrstunde für mich, würde mich interessieren, wie Ihr mit den Daten aus der MSDN arbeitet, damit das unter C# zum Erfolg wird.

Ich denke auch, so wie JimStark, daß du mit dem Ändern des ListView-Texts so nicht Erfolg haben wirst.
Ich hatte gestern schon mal, bezogen auf deinen anderen Beitrag, mit dem
>
versucht rauszufinden, wo die Daten für die Anzeige im "Windows-Fax- und -Scan" (WPS.exe) herkommen, aber die Zugriffsliste ist sehr lang (viele Registry-Zugriffe und diverse DLLs) - da müßte man noch weiter filtern... - das wäre m.E. der beste Weg, wenn man direkt die Daten (Datei, DB, Registry, ...) ändern würde.

Im Grunde kommen diese Daten aus den Dateinamen auf der Festplatte. Also das Standdard-Verzeichnis ist erst einmal "C:\Users&lt;User>\Documents\Scanned Documents". Von hier liest das Beim Starten die Dateinamen ein.

Vielleicht kann man ja auch das ListView dazu bringen, dass es dies wieder tut?

Die Methode SendMessageComplexGeneric scheint m.E. so nicht funktionieren zu können (basierend auf SendMessageString aus dem Link?), denn du müßtest ja irgendwie in der (generischen) Methode auf den Member pszText zugreifen.

PS: Der WinAPI-Wrapper unterstützt bisher aber noch keine ListView 😭

Die generische Methode arbeitet da ja mit den Strukturen. Diese haben zunächst einen Datentyp "string". Dieser wird im Ziel-Prozess in einem Speicherbereich alloziiert und man hat den Pointer darauf. Also meine Annahme war, dass das auch wirklich funktionieren könnte und wie aus der Struktur gefordert ein lpszText im Ziel-Speicher ist. So viel zur Theorie 😃

Eine weitere Idee, die aber etwas von hinten durch die Brust ins Auge ist: Mir ist aufgefallen, dass wenn ich in "Windows Fax- und -Scan" in dessen TreeView einen Ordner anlege und darauf klicke, das ListView ja erst einmal geleert wird. Benenne ich dann eine Datei auf der Festplatte um und klicke im TreeView wieder auf den Root-Knoten, dann wird im ListView der geänderte Dateiname angezeit!

Die Frage wäre, ob man sich das Zunutze machen kann:
Im TreeView (und vielleicht vorher auf der Platte) einen Dummy-Ordner anlegen, diesen dann per Button-Click anzuklicken (daraufhin ist das ListView leer). Datei auf der Platte umbenennen und danach per Button-Klick irgendwie wieder den Root-Knoten im TreeView selektieren. Dummy-Ordner im TreeView und der Platte wieder löschen.

Leider hab ich es noch nicht geschafft, per Win32-Api nen Ordner im TreeView unter dem Root-Knoten anzulegen 😦

07.06.2020 - 11:15 Uhr

Kann man das Control dazu bringen, sich neu zu initialisieren?

Oder eine andere Idee: Kann man irgendwie einen Message-Hook auf das ListView einrichten, so dass man vor dem Einfügen das Item manipulieren kann und dessen Text abändern kann, so dass das so an das Control übergeben wird?

Für diese Variante, würde man da auch ein Override auf das WndProc des eigenen Forms machen und kann sich irgendwie für bestimmte Nachrichten des entsprechenden ListViews registrieren?

07.06.2020 - 10:14 Uhr

Du scheinst eine alte Struktur LV_ITEM zu benutzen - laut
>
wird die Struktur
>
(bzw. für Unicode/Widestrings
>
benutzt) - beachte die zusätzlichen Member.

Danke schon einmal für Deine Hilfe!

Ich muss dazu vielleicht noch eine dumme Frage stellen: woher weiß ich bei den Funktionen/Strukturen/Strings bzw. beim Marshalling, ob ich das in der A- oder W-Variante für (Unicode/Widestrings) verwenden muss?

Habe noch einen interessanten Artikel zu SendMessage und genau dieser Problematik gefunden. Es scheint tatsächlich so zu sein, wie ich vermutet habe. Meine Strings und die Pointer zeigen nach der Deklaration in meiner Anwendung eigentlich auf meinen lokalen Speicher im Adressbereich meiner Anwendung. Diese müssen aber auf Speicher im Adressbereich der Ziel-Anwendung/Prozess zeigen. Daher vermute ich auch, dass das bei mir immer einfach so abgestürzt ist:

https://www.andreas-reiff.de/2011/07/win32-sendmessage-die-einen-string-erwartet-von-c-an-einen-anderen-prozess-schicken/

Habe nun die Methoden aus diesem Artikel verwendet und zumindest einen Teilerfolg damit. Immerhin stürzt es nicht mehr ab und in der richtigen Zeile/Spalte wird was angezeigt. Naja, angezeigt ist nicht ganz richtig, denn leider kommt nur ein leerer Text an und ich rätsle gerade warum? Dachte, das liegt an der fehlenden Text-Länge oder an den Mask-Parametern. Setze ich aber eine Text-Länge, schmiert das Ganze leider auch wieder ab. Mit verschiedenen Mask-Parametern hab ich auch schon experimentiert. Irgendwie scheint es mir noch nicht zu gelingen:


struct tagLVITEMA
{
	public uint mask;
	public int iItem;
	public int iSubItem;
	public uint state;
	public uint stateMask;
	public string pszText;
	public int cchTextMax;
	public int iImage;
	public IntPtr lParam;
	public int iIndent;
	public int iGroupId;
	public uint cColumns;
	public IntPtr puColumns;
	public IntPtr piColFmt;
	public int iGroup;
}

struct tagLVITEMW
{
	public uint mask;
	public int iItem;
	public int iSubItem;
	public uint state;
	public uint stateMask;
	public string pszText;
	public int cchTextMax;
	public int iImage;
	public IntPtr lParam;
	public int iIndent;
	public int iGroupId;
	public uint cColumns;
	public IntPtr puColumns;
	public IntPtr piColFmt;
	public int iGroup;
}

var item = new tagLVITEMW
{
	mask = LVIF_TEXT,
	//cchTextMax = (int)MAX_LVMSTRING,
	pszText = "Hallo",
	iItem = 1,
	iSubItem = 1
};

var result = SendMessageComplexGeneric<tagLVITEMW>((int)hWnd, LVM_SETITEM, 0, ref item, SendMessageDirection._out);

Neben diesem Effekt mit dem leeren String ist mir noch etwas aufgefallen. Wenn ich in "Windows Fax- und -Scan" im TreeView daneben auf einen Ordner klicke und das ListView mit anderen Daten gefüllt wird, und ich danach wieder zurück wechsle, so werden wieder die alten Daten im ListView angezeigt und mein "leerer Wert" in der Zelle ist wieder überbügelt mit dem alten Wert.

Die Frage ist, schaffe ich es durch das LVM_SETITEM überhaupt, die eigentlichen Daten des ListView abzuändern? Auf der Festplatte heißt die Datei bereits anders, wird aber mit dieser Aktion im ListView immer noch mit dem alten Wert angezeigt. Scheint so, als ob das ListView hier etwas cached und ich müsste diesen Wert ja irgendwie abändern.

06.06.2020 - 17:58 Uhr

Hallo zusammen,

hoffe, mir kann hier jemand weiterhelfen, denn langsam gehen mir die Ideen aus und ich weiß leider nicht, was ich falsch mache.

Hier zu meinem Problem:

Ich möchte einen Text in einem ListView einer anderen Anwendung (anderer Prozess), konkret in "Windows-Fax- und -Scan", umbenennen. Leider scheitere ich am SendMessage-Aufruf und daraufhin wird "Windows-Fax- und -Scan" geschlossen.

Den Handle des ListView habe ich ermittet und deckt sich mit dem Handle, den Spy++ zeigt. Auch ein SendMessage mit LVM_GETITEMCOUNT liefert die richtige Anzahl Elemente. Ein weiterer Test, eine Zeile aus dem ListView per SendMessage zu löschen, hat auch funktioniert. Gehe davon aus, dass der Handle schon mal passt.

Leider scheitere ich mit dem SendMessage-Aufruf, um den Text für den ersten SubItem des ersten Eintrags abzuändern.

Meine Code-Fragmente schauen aktuell so aus:


[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string lclassName, string windowTitle);

[DllImport("user32.dll", SetLastError = true)]
private static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);

[DllImport("user32.dll", SetLastError = true)]
private static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, ref LV_ITEM item_info);

private struct LV_ITEM
{
    public UInt32 uiMask;
    public Int32 iItem;
    public Int32 iSubItem;
    public UInt32 uiState;
    public UInt32 uiStateMask;
    public string pszText;
    public Int32 cchTextMax;
    public Int32 iImage;
    public IntPtr lParam;
};

public const Int32 LVM_FIRST = 0x1000;
public const Int32 LVM_SETITEM = LVM_FIRST + 6;
public const Int32 LVIF_TEXT = 0x1;

private IntPtr FindMDIWindowByIndex(IntPtr hWndParent, int index)
{
    if (index == 0)
        return hWndParent;
    else
    {
        int ct = 0;
        IntPtr result = IntPtr.Zero;
        do
        {
            result = FindWindowEx(hWndParent, result, "AfxMDIFrame42u", null);
            if (result != IntPtr.Zero)
                ++ct;
        }
        while (ct < index && result != IntPtr.Zero);
        return result;
    }
}


// Get Handle of ListView
string windowTitle = "Windows-Fax und -Scan";

IntPtr ptrWnd = FindWindow(null, windowTitle);
IntPtr ptrWndMDIWindow = FindWindowEx(ptrWnd, IntPtr.Zero, "AfxMDIFrame42u", null);
IntPtr ptrWndNestedMDIWindow2 = FindMDIWindowByIndex(ptrWndMDIWindow, 2);
IntPtr ptrWndListView = FindWindowEx(ptrWndNestedMDIWindow2, IntPtr.Zero, "SysListView32", null);

LV_ITEM lvi = new LV_ITEM();
lvi.iItem = 0;             // Row.
lvi.iSubItem = 0;          // Column.
lvi.uiMask = LVIF_TEXT;    // Want to set the Text.
lvi.pszText = "Item 0";    // Text to display
             
// Send the LVM_SETITEM message.
int result = SendMessage(ptrWndListView, LVM_SETITEM, 0, ref lvi);

Meine Vermutung aktuell ist, dass das etwas mit den verschiedenen Speicherbereichen meiner Anwendung und dem fremden Prozess in dem "Windows-Fax- und -Scan" läuft zu tun hat.

Wie bekomme ich das denn realisiert, dass der Text in diesem ListView abgeändert wird? Auch ist mir unklar, wie ich ganz allgemein Strings von A nach B transportieren muss in diesem Fall?

Bin für jeden Hinweis dankbar!

05.06.2020 - 19:38 Uhr

Leider sehe ich schon keine Informationen in Windows, die diese Information enthalten.

Habe noch mit der WindowsAPICodePack-ShellExtension Informationen ausgelesen, aber leider findet sich diese auch nicht bei den ganzen DeviceInformationen. Zumindest finde ich sie dort nirgends 😦

04.06.2020 - 22:11 Uhr

Hallo zusammen,

in "Windows Fax- und Scan" sehe ich nach dem Scannen eines Dokumens/Bilds in der Spalte "Qulle" den Scanner, der diese Datei gescannt hat.

Irgendwo muss diese Information in der Datei auch ausgelesen bzw. gesetzt werden können.

Wie kann ich dies aus der Datei nachträglich per C# auslesen bzw. abändern?