TRT
TRT

Reputation: 13

How to reload a tableview linked to an array controller?

I'm new to Cocoa, and I'm trying to create an application to audit our network devices by scanning our DHCP server log for ethernet hardware addresses and noting the most recent transaction. I've created a document-based program that will do that, and it displays its results (a mutable array of mutable dictionaries) in a tableView that is bound to an Array Controller.

I now want to read a file exported from the network IPAM manager, which consists of a list of ethernet hardware addresses allocated to our virtual LAN and compare that to the original array, adding in devices that are missing with a suitable annotation. I've done this with an action triggered by a button on the document (readSecondFile). There are also add and remove buttons for making a manual entry or, more usefully, for removing devices from the list. There's also a save routine for exporting the edited array.

It all works fine with one exception. When the second file is read in, the tableView will not redraw, reload its data or whatever until you click on a column heading to sort or add: or remove: using the ArrayController methods. The additional entries appear in the tableView when that happens.

My question is how do I get the tableView to redraw with the expanded array at the end of the readSecondFile routine? I've mooched around with KeyValueObserving, but I'm not sure that's required although it would work (if I could get it to work! I've asked the ArrayController to observe the array and notified of changes to the array, but I'm not sure if I need to do more.

Can anyone help? Many thanks.

[Additional] Just noticed, it does redraw, as it adds in some annotation for the existing devices in the array, but it doesn't display the additional devices that have IPAM entries but no DHCP activity.

[Additional - code requested]

//MyDocument.h
#import <Cocoa/Cocoa.h>
#import <stdio.h>
#import <regex.h>
#import <AppKit/AppKit.h>

typedef enum _dataFormat {
    Unknown = 0,
    PlainText = 1,
    RichText = 2,
    RTFD = 3,
} DataFormat;

int count, b, i, q, p;


@interface MyDocument : NSDocument
{
    NSMutableArray *macs;
    NSMutableDictionary *machines;
    NSArray *filteredLines;
    NSArray *lines;
    IBOutlet NSTextField *processing;
    IBOutlet NSProgressIndicator *myProgress;
    IBOutlet NSButton *removeButton;
    IBOutlet NSButton *readSecondFile;
    IBOutlet NSTableView *tableView;
    NSString *fileString;
}

-(IBAction)readSecondFileFromURL:(id)sender;

-(NSMutableArray *)macs;
-(NSMutableDictionary *)machines;
-(void)setMacs:(NSArray *)newMacs;
-(void)setMachines:(NSMutableDictionary *)newMachines;
-(NSArray *)filteredLines;
-(NSArray *)lines;


@end


//  MyDocument.m

#import "MyDocument.h"
#import "dataArrayController.h"

@implementation MyDocument

- (id)init
{
    self = [super init];
    if (self) {
        [self setMacs:[NSMutableArray array]];    
    }
    //[dataArrayController bind:@"macs" // see update
    //             toObject:self
    //          withKeyPath:@"mac"
    //              options:0];

    // set up an observer for arrangedObjects
    [dataArrayController addObserver:self
                      forKeyPath:@"mac"
                         options:0
                         context:nil];

    return self;
}

-(void)dealloc
{
    [self setMacs:nil];
    [super dealloc];
}

-(NSArray *)macs
{
    return macs;
}

-(NSArray *)filteredLines
{
    return filteredLines;
}

-(NSArray *)lines
{
    return lines;
}

-(NSMutableDictionary *)machines
{
    return machines;
}

-(void)setMacs:(NSMutableArray *)newMacs
{
    [newMacs retain];
    [macs release];
    macs=newMacs;
    //[macs setArray:newMacs];
}

-(void)setMachines:(NSMutableDictionary *)newMachines
{
    [newMachines retain];
    [machines release];
    machines = newMachines;
    [newMachines release];
}

-(void)setLines:(NSArray *)newLines
{
    [newLines retain];
    [filteredLines release];
    filteredLines = newLines;
}

- (NSString *)windowNibName
{
      return @"MyDocument";
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    [tableView reloadData];
}

-(BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError //Write a text file of the array (macs) contents 
{
    NSMutableString *csvString = [NSMutableString string];
    NSEnumerator *macEnum = [macs objectEnumerator];
    id data;
    while (data = [macEnum nextObject] ){
        NSNumber *mac = [data valueForKey:@"mac"];
        NSNumber *ack = [data valueForKey:@"ack"];
        NSString *vLan = [data valueForKey:@"vLan"];
        [csvString appendString:[NSString stringWithFormat:@"%@,%@,%@\n", mac, ack, vLan]];
    }
    return [csvString writeToURL:absoluteURL atomically:NO encoding:NSUTF8StringEncoding error:outError];
}

-(BOOL)readFromURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError //Check the log file existance
{
    fileString = [NSString stringWithContentsOfURL:absoluteURL encoding:NSUTF8StringEncoding error:outError];
    if (nil == fileString) return NO;
    return YES;
}

-(BOOL)readSecondFileFromURL:(id)sender //error:(NSError **)outError//(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError
{
    double progress;
    NSString* string = @"00:00:00:00:00:00";
    NSString* pattern = @"[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}";
    NSString* result = nil;
    NSString* listItem = nil;   
    NSString* mac = nil;
    NSArray* extracted = nil;
    NSMutableDictionary* obj=nil;
    NSDate *date = NULL;
    NSOpenPanel* openDlg = [NSOpenPanel openPanel];
    [openDlg setCanChooseFiles:YES];
    [openDlg setCanChooseDirectories:YES];

    if ( [openDlg runModalForDirectory:nil file:nil] == NSOKButton )
     {
        NSArray* files = [openDlg filenames];
//NSLog(@"%@",[files description]);
        // Loop through all the files and process them.
        for( b = 0; b < [files count]; b++ )
         {
            extracted = [macs valueForKey:@"mac"];
//NSLog(@"%@", [extracted description]);
            NSString* fileLoc = [files objectAtIndex:b];
//NSLog(@"%@",fileLoc);
            fileString = [NSString stringWithContentsOfFile:fileLoc encoding:NSUTF8StringEncoding error:nil]; 
            if (fileString == nil) return NO;

            lines = [fileString componentsSeparatedByString:@"\n"]; // each line, adjust character for line endings
            [processing setStringValue:@"Processing..."];
            [processing display];

            filteredLines = lines; //[lines filteredArrayUsingPredicate:pred];
            count = [filteredLines count];
//NSLog(@"%d",count);
//NSLog(@"%@",[filteredLines description]);

            [myProgress setDoubleValue:0.5];
            [myProgress setIndeterminate:NO];
            [myProgress displayIfNeeded];

            i=0;
            listItem  = [[NSString alloc] initWithString: @""];

            for (NSString *ent in filteredLines) {
                string=ent;
                result=NULL;
                listItem=@"";
                i++;
                progress = 100*((double)i/(double)count);
                q = progress;
//NSLog(@"%d",i);
                if (q > p) {
                    [myProgress setDoubleValue:progress];
                    [myProgress displayIfNeeded];
                    p = q;
                } //draw the progress bar 
//NSLog(@"%@",string);                          
                regex_t preg;           
//NSLog(@"B:%@",string);
                int err=regcomp(&preg,[pattern UTF8String],REG_EXTENDED);
                if(err) {
                    char errbuf[256];
                    regerror(err,&preg,errbuf,sizeof(errbuf));
                    [NSException raise:@"CSRegexException"
                                format:@"Could not compile regex %@: %s",pattern,errbuf];
                } //compile the regular expression
                //NSLog(@"C:%@",string);

                const char *cstr=[string UTF8String];
                regmatch_t match;
                if(regexec(&preg,cstr,1,&match,0)==0) {
                    result = [[[NSString alloc] initWithBytes:cstr+match.rm_so
                                                       length:match.rm_eo-match.rm_so encoding:NSUTF8StringEncoding] autorelease];
                    //NSLog(@"Result: %@",result);
                } //pull out the ethernet hardware address by scanning for the regex
//NSLog(@"D:%@",result);
                if(result != NULL){
                    if(result.length < 17) {
                    NSArray *listItems = [result componentsSeparatedByString:@":"];
                    for (NSString *bytepair in listItems){
                        //NSLog(@"split: %@", bytepair);
                        unsigned result;
                        if([[NSScanner scannerWithString: bytepair] scanHexInt: &result])
                            listItem=[listItem stringByAppendingFormat:@"%02x:", (int)result];
                        } //break address into array at colons
                    result=[listItem substringToIndex:17] ;
                    } //pad out to standard ethernet hardware address
                    mac = result;   
//NSLog(@"%@ %d",mac, [extracted containsObject:mac]);
                    if ([extracted containsObject:mac]){
                        for(id key in macs) {
                                if ([[key valueForKey:@"mac"] isEqualToString:[NSString stringWithFormat:@"%@", mac]]) {
                                   [key setValue:@"vLan OK" forKey:@"vLan"];    
                                } //Check if the mac is already registered and if so annotate it.
                         } //Annotate an existing entry
                    }   else {
                        date=[NSDate distantPast];
                        obj=[NSMutableDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"%@",mac], @"mac", date, @"ack", @"No DHCP activity", @"vLan", nil];
                        NSIndexSet *loneIndex = [NSIndexSet indexSetWithIndex:[macs count]];
                        [dataArrayController willChange:NSKeyValueChangeInsertion valuesAtIndexes:loneIndex forKey:@"mac"];
                        [macs addObject:obj];
                        [dataArrayController didChange:NSKeyValueChangeInsertion valuesAtIndexes:loneIndex forKey:@"mac"];
                    }   //Add a new entry           
                }

            }

            //[myProgress setIndeterminate:YES];
            //[myProgress startAnimation:self];
            //[myProgress displayIfNeeded];
//NSLog(@"%@",[newMachines description]);
            //[self setMachines:newMachines];
//NSLog(@"%@",[machines description]);
            //mac = NULL;
            //for (mac in machines) {
            //  ack = [machines valueForKey:mac];
            //  NSLog(@"F:%@", mac);
                //[newMacs addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"%@",mac], @"mac", ack, @"ack", @"vLan", @"vLan", nil]];
            //} //Convert the machine dictionary into a format suitable for an array controller
            //[mac release];
            //[machines release];
            //[newMachines release];
            //[self setMacs:newMacs];
            //[dateFormat release];
            //[myProgress setDisplayedWhenStopped:NO];
            //[myProgress stopAnimation:self];
            //[myProgress setHidden:YES];
            //[processing setHidden:YES];
            //if ([macs count]>0){
            //  [removeButton setHidden:NO];
            //}
            [readSecondFile setHidden:NO];
            [readSecondFile display];
            //[extracted release];
                     }
     }

    [tableView reloadData];

    return YES;
}

- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{   [super windowControllerDidLoadNib:aController];
    [self showWindows];
    [removeButton setHidden:YES];
    [removeButton display];
    [readSecondFile setHidden:YES];
    [readSecondFile display];
    [processing setStringValue:@"Loading..."];
    [processing setHidden:NO];
    [processing display];
    [myProgress setHidden:NO];
    [myProgress setUsesThreadedAnimation:YES];
    [myProgress setIndeterminate:YES];
    [myProgress startAnimation:self];

    int count, i, q, p;
    double progress;

    NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF CONTAINS 'DHCP'"];  

    NSString* string = @"00:00:00:00:00:00";
    NSString* pattern = @"[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}";
    NSString* result = nil;
    NSString* listItem = nil;   
    NSString* mac = nil;
    NSString* ack = nil;    
    NSString* localeDetect = nil;
    NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];   // Convert string to date object
    NSDate *date = NULL;
    NSRange rangeForLocaleDetect = NSMakeRange (20, 3);
    NSMutableArray *newMacs = [NSMutableArray array];

    lines = [fileString componentsSeparatedByString:@"\n"]; // each line, adjust character for line endings
    [processing setStringValue:@"Processing..."];
    [processing display];

    filteredLines = [lines filteredArrayUsingPredicate:pred];
    count = [filteredLines count];

    NSMutableDictionary *newMachines = [NSMutableDictionary dictionaryWithCapacity:count];

    [myProgress setDoubleValue:0.5];
    [myProgress setIndeterminate:NO];
    [myProgress displayIfNeeded];

    i=0;
    p=0;

    for (NSString *ent in filteredLines) {
        string=ent;
        i++;
        progress = 100*((double)i/(double)count);
        q = progress;
        if (q > p) {
            [myProgress setDoubleValue:progress];
            [myProgress displayIfNeeded];
            p = q;
         } //draw the progress bar 

        listItem  = [[NSString alloc] initWithString: @""];
        localeDetect = [NSString stringWithFormat:@"%@",[string substringWithRange:rangeForLocaleDetect]];

        if ([localeDetect isEqualToString:@"UTC"]){
            [dateFormat setDateFormat:@"yyyy.MM.dd HH:mm:ss"];
            ack = [NSString stringWithFormat:@"%@", [string substringToIndex:19]];
        } else {
            [dateFormat setDateFormat:@"MMM dd HH:mm:ss"];
            ack = [NSString stringWithFormat:@"%@",[string substringToIndex:15]];

        }

        date = [dateFormat dateFromString:ack];
        regex_t preg;
        int err=regcomp(&preg,[pattern UTF8String],REG_EXTENDED);
        if(err) {
            char errbuf[256];
            regerror(err,&preg,errbuf,sizeof(errbuf));
            [NSException raise:@"CSRegexException"
                        format:@"Could not compile regex %@: %s",pattern,errbuf];
         } //compile the regular expression

        const char *cstr=[string UTF8String];
        regmatch_t match;
        if(regexec(&preg,cstr,1,&match,0)==0) {
            result = [[[NSString alloc] initWithBytes:cstr+match.rm_so
                                               length:match.rm_eo-match.rm_so encoding:NSUTF8StringEncoding] autorelease];
         } //pull out the ethernet hardware address by scanning for the regex

        if(result.length < 17) {
            NSArray *listItems = [result componentsSeparatedByString:@":"];
            for (NSString *bytepair in listItems){
                //NSLog(@"split: %@", bytepair);
                unsigned result;
                if([[NSScanner scannerWithString: bytepair] scanHexInt: &result])
                    listItem=[listItem stringByAppendingFormat:@"%02x:", (int)result];
             } //break address into array at colons
            result=[listItem substringToIndex:17] ;
        } //pad out to standard ethernet hardware address

        mac = result;
        NSDate *old = [newMachines valueForKey:mac];
            if (([date laterDate:old]==date) || old==NULL){
                [newMachines setValue:date forKey:mac];     
            } //Check for and compare dates for existing keys, as the log file might just be out of sequence
    } //For every line in the log which meets the Predicate, extract the relevant information.

    [myProgress setIndeterminate:YES];
    [myProgress startAnimation:self];
    [myProgress displayIfNeeded];

    [self setMachines:newMachines];

    for (mac in machines) {
        ack = [machines valueForKey:mac];
        [newMacs addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"%@",mac], @"mac", ack, @"ack", @"", @"vLan", nil]];
        } //Convert the machine dictionary into a format suitable for an array controller

    [self setMacs:newMacs];
    [dateFormat release];

    [myProgress setDisplayedWhenStopped:NO];
    [myProgress stopAnimation:self];
    [myProgress setHidden:YES];
    [processing setHidden:YES];
    if ([macs count]>0){
        [removeButton setHidden:NO];
    }
    [readSecondFile setHidden:NO];
    [readSecondFile display];


} //Process the file and display the relevant progress bars. Create and populate array.

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
    // Insert code here to write your document to data of the specified type. If the given outError != NULL, ensure that you set *outError when returning nil.
    // You can also choose to override -fileWrapperOfType:error:, -writeToURL:ofType:error:, or -writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead.
    // For applications targeted for Panther or earlier systems, you should use the deprecated API -dataRepresentationOfType:. In this case you can also choose to override -fileWrapperRepresentationOfType: or -writeToFile:ofType: instead.

    if ( outError != NULL ) {
        *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:NULL];
    }
    return nil;
}

- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
    // Insert code here to read your document from the given data of the specified type.  If the given outError != NULL, ensure that you set *outError when returning NO.
    // You can also choose to override -readFromFileWrapper:ofType:error: or -readFromURL:ofType:error: instead.     
    // For applications targeted for Panther or earlier systems, you should use the deprecated API -loadDataRepresentation:ofType. In this case you can also choose to override -readFromFile:ofType: or -loadFileWrapperRepresentation:ofType: instead.

    if ( outError != NULL ) {
        *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:NULL];
    }
    return YES;
}

@end

Upvotes: 0

Views: 1466

Answers (2)

TRT
TRT

Reputation: 13

The correct approach was to use the ArrayController to add new objects to the array which tableView was displaying.

ArrayController was not responding to those methods because I had subclassed it during development work and inadvertently not removed that subclassing later. noa's comments proved invaluable at prompting me to reexamine my code. Many thanks.

Upvotes: 1

paulmelnikow
paulmelnikow

Reputation: 17208

It's pretty simple:

[tableView reloadData];

If you don't have a pointer to the table view, create and connect an outlet in your controller class.


You asked for some suggestions. Here are a few:

  1. Add a breakpoint on all exception throws.
  2. Take a look at Objective-C properties. It saves you from having to write accessor and setter methods.
  3. Try to refactor your code to eliminate the array controller subclass. (I think the problem you had with the array controller/table view updates might have been related to the interface between two classes. If it were all handled inside your controller class using the standard array controller it might have been easier to understand.)
  4. For consistency with Apple's naming conventions, begin your class names with capital letters and your instance variables with lower case letters.

Upvotes: 0

Related Questions