C# code against Gmail

I wrote some code and I FINISHED IT

UPDATE – After writing this blog post, about two hours later, I got the stuff working for it to be considered “DONE”. Pretty happy with it. The rest of this post, though, accurately reflects how I was feeling at the time, so i’m leaving it as written.

I wrote this code, and I am Sad. I’m sad that i got started on this silly project (that i’ve thought about for several years), and I got far enough that I can see the end … and now:

ONE: I’m out of time. I did this while we were on our 1-week-away vacation… it started slowly, but got faster and better. This is probably 6-7 hours in? Spread over 3 evenings? I rediscovered so many things (list later), but .. I have a maximum of 4 more hours left before I return to normal life which has NO room for focus time like this (which is something I could change/prioritize, but I don’t because I am finally prioritizing adequate sleep).

TWO: I’m not done, and I’m already rewriting and improving it. So why finish it in this incarnation? I realized with the approach I’m taking, it deals with “recent” emails pretty good, and it does “discovery” pretty good, but it does not handle “the massive backlog of crap that could be deleted”. Nope, that would be a different approach .. in the above screenshot, i think this would be the “Discovery” tab and then there would be another “Purge-atory” tab for the deep cleanse cycle.

Anyway, what is it? Source is here: https://github.com/sunnywiz/gmail-filter-2024/tree/blog-post-1

  • It uses OAuth to connect to Gmail
  • It downloads the last N days of email and stores just a bit of it in a local cache
  • It looks for senders who send me lots of stuff.
  • It lets me choose to keep only the most recent N things per that sender and delete the rest (from that sender)

Along the way, I got to revisit some technology stuff:

  • I started in Console for .Net 6, switched back .Net Framework 32bit talking to Outlook via interop for just a bit, realized that was also painfully slow, and switched back to Gmail’s nuget package in .Net 6 with WPF. Thank God, at my workplace i’ve been entirely in “Framework” and its nice to see where (what was .Net Core and is not .Net) has gotten.
  • I wrote it somewhat with JetBrains AI Assistant, mostly looking up sample code on how to do stuff like get the list of emails from Gmail, wire up a TwoWay binding, etc.
  • I had to reremember a lot of the WPF stuff.. haven’t worked in that since 2016? 2018? … There was this trick of setting up a Debug Type converter that I had to use to fix a binding, turns out I needed to change the UpdateTrigger to be on property change rather than loose focus, everything was ok otherwise.
  • I remembered a trick of using a FlowDocument to lay out controls quickly in a way that looks okay-ish and is usable, rather than trying to get all those !@#!@# grid columns right.

So, for 6 hours spread over 3 days.. not too bad. Not 100% of where I wanted to get to. But i have a few more hours left yet. At half an hour per “session”, I might be able to get these things done – by the time you read this, this drama will be over:

  • I need to remember what the settings are for different from: email addresses, and save them, so i can auto-apply them later
  • I need a hands-off mode where it spins up, grabs email, and purges stuff.
  • I need a “keep 2 weeks” kind of thing for stuff like Paypal and Venmo transactions, and Patreon posts. I usually check on those things within 2 weeks.

And then there’s the nice-to-have:

  • Show the category that an email is in
  • Show if an email is read or unread
  • Option for “Mark all as Read” – stop cluttering my inbox notification counter!!!!
  • As mentioned above, the “Deep Clean” cycle – where it applies the rules to EVERYTHING from those senders – individual searches per sender, only get the details on the messages to keep, and everything else, i can send the messageId’s to the Trash() method.

Or, I could eat that Cinnamon Roll that I got and watch Youtube and call it done for the weekend.

Day at the Office: I’m Optimizing UX with SPEECH

Note: Say the title of this post in the same way as you would  “She Blinded Me With Science”

image

The Problem

The customer has a screen, which is used for data entry.  The source of the entry is various pieces of papers with scribbles on them.  On observing the person doing the data entry, what I saw was:

  • Look at paper to see WHO it was for
  • Look at screen to find the same.  Usually its the next one in order
  • Switch to Mouse
  • Click once to select the who
  • Click to select the thingies inside the who
  • Click the button to start the data entry for the who
  • Look at paper
  • Find the number
  • Switch to keyboard
  • Type in the number
  • Look at screen
  • Verify number
  • Press Enter to save
  • Look at paper
  • Move Hands to Paper
  • Mark Paper as done.
  • Move Paper Aside
  • Look at next paper.

Ouch.  We could do better.

Our (Final) Proposed Solution

I think its pretty cool, I don’t know yet if the user(s) will adopt it, but this is what we came up with.

  • User can keep right hand on numeric keypad, and arrow up to the first entry.  (they sort paper and computer entries to match beforehand)
  • Numeric / (on the keypad) starts entering for the currently highlighted row (even if it not yet selected)
  • As the data entry screen opens, the computer “speaks” the relevant info about what is being entered.  This is so the user can keep eyes on paper, while listening to verify that the right row was selected
  • Still keeping eyes on paper, user can type in the numbers on the keypad, and then press numeric + (on the keypad) to “speak” the numbers that were entered, for verification, if they want.
  • In case the user wants to look at the computer screen, we also made the window bigger, text larger, and used color to highlight the various corroborating pieces of information.
  • If all is good, user hits enter on the numeric keypad to save, and now the next row is highlighted.

Blocked

This is well and good, except, this is what happened in testing:

  • /  34.5 +  <enter> / 45 <enter>
  • When the user got to the second /, the hotkey did not fire.

I spent many hours trying to figure this one out.  Its complicated, I still don’t know the full answer, but it has to do with a DockedWindow, a control suite, a Busy indicator,  a Ribbon suite, sub-ribbons, and document panels, and when the modal popup went away, focus did not return to the correct window. 

Bottom Line: all my attempts to set focus were ignored.  Seriously.  I even bound a hotkey at the Shell level (the whole application) “F10” to fire code that should set the focus back to the correct window.. and.. nothing.

This problem also extended to any window.   Any window, when it first came up, did not have focus, so any hotkeys bound inside that window, would not fire.   Till it was clicked in.  Then it was okay. 

Hacked Solution

I could still capture hotkeys via the Shell, so this is what I did:

  • Shell catches PreviewKeyDown
  • Shell looks at current active docked window (since it hosts the dock control, this is easy)
  • If current active window implements IReallyWantKeyDownStuff, then call the YoHeresAKeyForYou(). (Names changed because i don’t have intellisense writing at home, but you get the idea).
  • My window receives this, and if it was Numeric/, then it fires the button that starts the data entry process

I used the same code to also wire some other hotkeys for some other screens.

Office Visit Still Needed

I hope to (soon) visit my client’s office in person, and sit down with the folks who would use this “enhancement”, and show them what I had thought.   As someone once told me, I have to be careful how many strings I have attached to this:  I have to be okay with them taking my awesome idea and putting it in the trash.  No strings attached.  So I’m blogging about its coolness now, in case its a flop, because right now, I think its pretty cool.

Breaking the Recursive NotifyOfPropertyChange()

Another problem I ran into in the WPF world were controls whose side effects led to a endless loop of updates:

image

The outer one is called an “Order”, and the inner one is called a “Trade”

The problem goes like this:

  • Click on the outer one to select everything
    • Inner ones turn on
    • But clicking an inner one, if all of them are turned on
      • selects the Outer one.
        • recursive loop to infinity.

There’s probably some really crazy way to set this stuff up (there seems to be no end of the kind of hoops people will jump through for WPF), but here was my solution:

        private bool _onOrderCheckedFiring; 

        private void OrderChecked(CheckBoxListItemThreeState<OrderListItem> obj)
        {
            if (_onTradeCheckedFiring) return; // we are a result of a trade check, so don't do anything.
            try
            {
                _onOrderCheckedFiring = true;
                foreach (var t in obj.Value.AllTrades)
                {
                    t.IsChecked = true;
                }
                NotifySelectedOrdersChangeProperties();
            }
            finally
            {
                _onOrderCheckedFiring = false;
            }
        }
        protected virtual void OnTradeChecked(CheckBoxListItem<HoldingBase> item)
        {
            if (_onOrderCheckedFiring) return; // result of order check, so don't do any more logic. 
            try
            {
                _onTradeCheckedFiring = true;
                var theOrder = Orders.FirstOrDefault(o => o.Value.Order.OrderID == item.Value.OrderID);
                if (theOrder != null)
                {
                    var areAllTradesChecked = theOrder.Value.AreAllTradesChecked;
                    var newState = theOrder.IsChecked;

                    if (areAllTradesChecked)
                        //all trades are checked; set the order checkbox to true
                        newState = true;
                    else if (theOrder.Value.AreAnyTradesChecked)
                        //not all trades are checked, but some are; set the order checkbox to indeterminate
                        newState = null;
                    else if (!theOrder.Value.AreAnyTradesChecked)
                        //no trades are checked; set the order checkbox to false
                        newState = false;

                    // ReSharper disable RedundantCheckBeforeAssignment
                    //NO, ReShaper, this is NOT a redundant check, because if the value is the same, I don't
                    //  want the notification events firing.
                    if (theOrder.IsChecked != newState)
                        // ReSharper restore RedundantCheckBeforeAssignment

                        theOrder.IsChecked = newState;

                    theOrder.Value.NotifyAreAllTradesCheckedChanged();
                }
                NotifySelectedOrdersChangeProperties();
            } finally {
                _onTradeCheckedFiring = false;
            }
        }

 

This code is not complete, and it was reformatted to reduce the number of lines to paste.  Basically, I just flag that I’m doing something intricate in one routine, and in the other routine, I see if the intricateness is happening, and if so, I DO NOT fire the thing that would make it recursive.

I’m almost done with this WPF project, I’ll get to go back to the Web World probably next week.  Before I fully forget WPF, I would like to make a chart of all the stuff I’ve learned similar to what I did for MVC.

VisualTreeHelper

I seem to have gotten away from posting any coding things on this blog.   At first I thought its because I really haven’t done anything exciting; most of the work has been “figure out customer’s need, create something that solves that need”.   Then as I dug deeper, I rediscovered some fun things that I had created along the way:

The Problem

There’s a screen with a list of grids (this is a stock trading app) – when tabbing out of one grid, it needed to get to the next grid.  I also needed to jump left and right from master to detail easily.   (Sorry about all the blurring, but those were real numbers):

image

The screen, in and of itself, was a solution to “how to balance a portfolio to get it back in line with sector totals easily”.   The user could enter a proposed change in a holding, see how the math worked, and then click save positions to generate all the trade(s) necessary to make it happen.

Solution

I needed to write code that would take the grid that was currently focused, figure out what the next grid was, and focus there.    when solved, it looks like this:

image

Oy! What is this?  It’s a helper function that I wrote.   Basically, Starting with a known visual element, I look for “stuff that I’m interested in” (if the lambda returns true, then I’m interested in it):

image

This cuts down the (very) complicated visual control tree (see: http://www.codeproject.com/Articles/21495/Understanding-the-Visual-Tree-and-Logical-Tree-in) down into something much more manageable (this is the output of the Dump() function defined below with some additional comments thrown in)

image

Here’s the source that does it:

        public static VisualTreeInterestingNode GetInterestingNodes(this FrameworkElement topDependencyObject, Func<FrameworkElement, bool> isInteresting)
        {
            var timer = new Stopwatch();
            timer.Start(); 

            var topInterestingNode = new VisualTreeInterestingNode() { FrameworkElement = topDependencyObject, Parent = null };

            var nodesToVisit = new LinkedList<Tuple<DependencyObject, VisualTreeInterestingNode>>();
            nodesToVisit.AddLast(new Tuple<DependencyObject, VisualTreeInterestingNode>(topDependencyObject, topInterestingNode));

            while (nodesToVisit.Any())
            {
                var a = nodesToVisit.First();
                nodesToVisit.RemoveFirst();

                var currentDependencyObject = a.Item1;
                var currentInterestingNode = a.Item2;

                int childCount = VisualTreeHelper.GetChildrenCount(currentDependencyObject);
                for (int i = 0; i < childCount; i++)
                {
                    var childDependencyObject = VisualTreeHelper.GetChild(currentDependencyObject, i);
                    if (childDependencyObject == null) continue;

                    var childFrameworkElement = childDependencyObject as FrameworkElement;

                    if (childFrameworkElement != null)
                    {
                        if (isInteresting(childFrameworkElement))
                        {
                            var b = new VisualTreeInterestingNode() { FrameworkElement = childFrameworkElement, Parent = currentInterestingNode };
                            currentInterestingNode.InterestingChildNodes.Add(b);
                            nodesToVisit.AddLast(new Tuple<DependencyObject, VisualTreeInterestingNode>(childDependencyObject, b));
                        }
                        else
                        {
                            nodesToVisit.AddLast(new Tuple<DependencyObject, VisualTreeInterestingNode>(childDependencyObject, currentInterestingNode));
                        }
                    }
                    else
                    {
                        nodesToVisit.AddLast(new Tuple<DependencyObject, VisualTreeInterestingNode>(childDependencyObject, currentInterestingNode));
                    }
                }
            }

            timer.Stop(); 

            return topInterestingNode;
        }

And the VisualTreeNode class that the result is in:

    public class VisualTreeInterestingNode
    {
        private List<VisualTreeInterestingNode> _all;

        public VisualTreeInterestingNode()
        {
            InterestingChildNodes = new List<VisualTreeInterestingNode>();
        }
        public FrameworkElement FrameworkElement { get; set; }
        public List<VisualTreeInterestingNode> InterestingChildNodes { get; set; }
        public VisualTreeInterestingNode Parent { get; set; }

        public List<VisualTreeInterestingNode> Flatten
        {
            get
            {
                if (_all != null) return _all;

                _all = new List<VisualTreeInterestingNode> { this };
                foreach (var child in InterestingChildNodes)
                    _all.AddRange(child.Flatten);

                return _all;
            }
        }

        public StringBuilder Dump(StringBuilder prefix = null, StringBuilder result = null)
        {
            if (prefix == null) prefix = new StringBuilder();
            if (result == null) result = new StringBuilder();

            result.Append(prefix).Append(FrameworkElement.GetType().Name);
            result.Append(" ").Append(FrameworkElement.Name);
            result.AppendLine();

            var l = prefix.Length;
            prefix.Append("  ");
            foreach (var node in InterestingChildNodes)
            {
                node.Dump(prefix, result);
            }
            prefix.Length = l;
            return result;
        }
    }