Метод Excel TextToColumns в VBA для игнорирования формул

Во-первых, позвольте мне сказать, что я надеюсь, что спрашивать об этом в том же посте, что и мой оригинал, является правильным этикетом SO. Все в том же духе, но я не уверен, что это правильно. Так что, пожалуйста, если я не прав, поправьте меня, чтобы я мог это исправить.

Я создал макрос для выполнения TextToColumns некоторое время назад. Я отредактировал и почистил макрос. Он работал нормально, пока я не начал распространять данные в эти поля с помощью формул вместо того, чтобы копировать данные в них вручную, как я делал раньше.

Теперь я расширил код модуля моего рабочего листа и понял, что метод, который я написал несколько месяцев назад, мешает формулам, которые теперь копируют значения в поля, которые я изначально выполнял TextToColumns.

Короче говоря, есть ли способ заставить .TextToColumns принимать значения диапазона, в котором он работает, и игнорировать формулу?

 Sheet4.Range(categorySrcColumn & srcCategoryColLastRow)
       .TextToColumns _
            Destination:=Range(categoryDestColumn), _
            Other:=True, _
            OtherChar:="/", _
            FieldInfo:=Array(1,1), _
            TrailingMinusNumbers:=true

Я проверил документацию для TextToColumns в поисках xlValues, но ничего подобного не нашел.

---------- дополнение -----------

Рэйчел. Я ценю ваш вклад. Возможно, вы могли бы помочь немного больше. Я подключил ваше предложение, но оно просто ничего не делает. Я позволил себе несколько вольностей в надежде объединить то, что вы дали, с тем, что мне нужно. Отказ от ответственности: я ни в коем случае не разработчик VBA. То, что я знаю, это то, что я узнал из Google. Я живу в мире Unix/Linux, так что это совсем не то, к чему я привык.

Вот что у меня пока...

Public Function sbTextToColumn(srcRange As String, dstRange As String)
    Dim vData As Variant

    vData = Split(ActiveSheet.Range(srcRange).Value, "/")

    ' I need to massage the data in the srcRange a bit before I can separate it out. 
    ' This is a partial sanity check on the data as well.
    Range(srcRange).Replace What:="I/R", Replacement:="IR", Lookat:=xlPart, _
                    SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=False _
                    ReplaceFormat:=False

    Range(srcRange).Replace What:="N/A", Replacement:="NA", Lookat:=xlPart, _
                    SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=False _
                    ReplaceFormat:=False

    Range(srcRange).Replace What:=" ", Replacement:="", Lookat:=xlPart, _
                    SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=False _
                    ReplaceFormat:=False

    ActiveSheet.Range(dstRange).Resize(ColumnSize:=UBound(vData) + 1) = vData
    sbTextToColumn = ""
End Function

К сожалению, я не могу выложить пример таблицы. Он находится в закрытой, защищенной сети.

Теперь, когда я подключаю это к рабочему листу через: =sbTextToColumn(C836, E836), ничего не происходит.

Это моя первая попытка создать и использовать UDF. Вот как я вызываю функцию. У меня есть рабочий лист с первыми тремя столбцами A-C, извлекающими данные из другой электронной таблицы. Эти столбцы, конечно, формулы. Столбец D не используется, но я размещаю здесь вызов UDF, надеясь, что это сработает. Столбцы E-I зарезервированы для данных T2C, а столбец J используется для извлечения тех же данных, что и столбец B (да, я знаю, что это избыточно). Моя цель состоит в том, что я хотел бы избавиться от столбца J (он используется для vlookup на другом листе, и я, возможно, не смогу его удалить, но я бы хотел). В идеале я хотел бы извлечь исходные данные из другой электронной таблицы, проверить их на работоспособность, разделить и просто поместить отсортированные данные в необходимые столбцы n.

Итак, я на правильном пути? Я все еще работаю над этим. Если я добьюсь прогресса, я опубликую прогресс. ТИА!


person Jim    schedule 21.12.2011    source источник
comment
В порядке. Итак, я собираюсь пойти на риск и сказать, что у меня нет возможности сделать это. Я решил попробовать атаковать это с другой стороны. Поток этого небольшого моего проекта примерно следующий: исходная таблица -> таблица статистики -> графики статистики. Текстовые столбцы, которые я сейчас выполняю, происходят в части электронной таблицы статистики, значения которой теперь поступают из формул в этой электронной таблице. Теперь я собираюсь запустить texttocolumns для исходных данных, а не для рабочего листа назначения. Посмотрим, как я справедлив. У меня, вероятно, возникнут дополнительные вопросы, связанные с этой методологией.   -  person Jim    schedule 22.12.2011


Ответы (2)


Функции, включая пользовательские функции, могут возвращать значение только вызывающей стороне. Они не могут изменять другие ячейки.

Вот метод Рэйчел, завернутый в UDF:

Function sbTextToColumn(rng As Range) As Variant
    Dim vData As Variant

    vData = Split(rng.Value2, "/")
    ReDim Preserve vData(0 To Application.Caller.Columns.Count - 1)
    sbTextToColumn = vData
End Function

Вы вводите его как функцию массива. В вашем примере, скажем, для row 2

  • Выберите диапазон E2:I1
  • Введите =sbTextToColumn(C2)
  • Нажмите Ctrl+Shift+Enter
  • Скопируйте диапазон из 5 ячеек вниз для необходимого количества строк.

Если функции не могут изменять другие ячейки, это означает, что вы не можете выполнять очистку в пользовательской функции.

Из вашего примера кода вы хотите

  • Замените I/R на IR, а N/A на NA.
  • Удалить пробелы
  • Сделайте это после того, как данные будут разбиты на столбцы E:I (таким образом I/R уже будут разделены на две ячейки)
  • Замените исходные формулы столбца C очищенными данными в виде значений.

Это правильно?

Если это так, вам лучше вообще не использовать UDF, а использовать Sub (вызывается из быстрой клавиши или добавляется в меню)

Что-то вроде:

Sub sbTextToColumn()
    Dim ws As Worksheet
    Dim srcRng As Range
    Dim rw As Range
    Dim vData As Variant
    Dim srcData As Variant
    Dim dstData As Variant
    Dim i As Long, j As Long
    Dim maxCol As Long

    Set ws = ActiveSheet
    Set srcRng = Range(ws.Columns(3).Cells(1, 1), _
        ws.Columns(3).Cells(ws.Columns(3).Rows.Count).End(xlUp))

    srcData = srcRng
    ReDim dstData(LBound(srcData, 1) To UBound(srcData, 1), 1 To 5)

    For i = LBound(srcData, 1) To UBound(srcData, 1)
        If srcData(i, 1) <> "" Then
            ' Split data
            vData = Split(srcData(i, 1), "/")
            maxCol = UBound(vData, 1)
            If maxCol > 5 Then maxCol = 5
            For j = 1 To maxCol + 1
                dstData(i, j) = vData(j - 1)
            Next

            ' Sanitise data
            srcData(i, 1) = Replace(srcData(i, 1), "I/R", "IR", Compare:=vbTextCompare)
            srcData(i, 1) = Replace(srcData(i, 1), "N/A", "NA", Compare:=vbTextCompare)
            srcData(i, 1) = Replace(srcData(i, 1), " ", "", Compare:=vbTextCompare)
        End If
    Next

    ' Put results back in sheet
    srcRng = srcData
    srcRng.Offset(0, 2).Resize(, 5) = dstData
End Sub

Если вы действительно хотите провести очистку перед разделением, просто поменяйте местами блоки кода «Разделить данные» и «Очистка данных».

person chris neilsen    schedule 22.12.2011
comment
Мы приближаемся. Спасибо за ваш ответ. Итак, мой пример кода был плохо написан вашим кратким описанием того, что я пытался сделать на основе своего кода. На самом деле это должно быть в последовательности: сначала исправить N/A, I/R и пробелы, а затем разделить поле, разделенное символом «/», поместив результат в отдельные n ячеек одной строки. Что касается столбца C, было бы идеально, если бы я мог поместить формулу в ту ячейку, которая запускает Sub. Значение первого элемента массива в результате подпрограммы будет в C.value2, но формула останется неизменной. Я очень ценю вашу помощь!! - person Jim; 23.12.2011
comment
Рад помочь, Джим. Есть несколько более тонких моментов, которые нам нужно отсортировать, прежде чем двигаться вперед: 1. всегда ли N/A и I/R в верхнем регистре, или следует также заменить n/a и т. д. 2. что-то вроде aN/Another считается как N/A 3. может ли дезинфицирующая часть быть встроена в формула в столбце C, возможно, путем преобразования ее в UDF 4. максимальное количество / разделенных компонентов, ограниченное 5, или решение должно обрабатывать любое число, или какое-то другое большее максимальное число - person chris neilsen; 23.12.2011
comment
(продолжение) 5. Наличие реакции Excel на ввод формулы в ячейку возможно с событием Change на листе. По сути, событие запускается всякий раз, когда что-либо на листе изменяется, и идентифицирует измененные диапазоны. Затем ваша логика определяет, соответствует ли изменение некоторым критериям, а затем выполняет какое-то действие. Звучит как жизнеспособное решение? - person chris neilsen; 23.12.2011
comment
В порядке. 1. Вероятно, они должны быть нечувствительны к регистру. 2. Нет. Это всегда будет NA, IR или какой-то другой небольшой категориальный термин, который даже близко не подходит к слову. 3. Не вижу в этом проблемы. Я бы предпочел, чтобы это было как можно более автоматическим, без необходимости, чтобы пользователи запускали событие вручную. 4. В настоящее время существует ограничение в пять, и оно, вероятно, никогда не будет достигнуто, но это ограничение, вероятно, следует снять на всякий случай. Еще раз, я очень ценю вашу помощь. Это была настоящая боль! :) - person Jim; 23.12.2011
comment
Я вижу два пути вперед: 1. использовать формулы. Чтобы очистить ваши данные, либо оберните существующую форму в =SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(YourFormula,"N/A","NA"),"I/R","IR")," ",""), либо преобразуйте ее в UDF. Используйте версию UDF в виде массива, как указано выше, для разделения данных. Примените его к подходящему диапазону, чтобы учесть максимальное практическое количество результатов. Или 2. пойти по маршруту события. Это усложнится, и, хотя после работы это будет легко для пользователя, это будет PITA для обслуживания. Лично я бы, вероятно, выбрал вариант 1 и преобразовал вашу существующую формулу в UDF со встроенной очисткой. - person chris neilsen; 23.12.2011
comment
Тогда давайте по варианту 1. Я не знал, что могу это сделать. Тогда формула будет такой, какую предложила Рэйчел? Я думаю, что могу это сделать, и у меня нет проблем с подавлением вывода до 5 столбцов. шансы на то, что данные станут больше, действительно очень редки! По крайней мере, я знаю, что решить эту проблему было непросто. - person Jim; 23.12.2011
comment
Используйте версию, как описано в верхней части моего ответа. Я сделал небольшое редактирование, чтобы избежать возврата N/A'a, если в результате меньше 5 значений. - person chris neilsen; 23.12.2011
comment
Итак, позвольте мне посмотреть, понимаю ли я, что происходит. Формула (я назвал sbTextToColumns) вызывается первой по приоритету. Он извлекает данные из моей исходной электронной таблицы, разбивает строки с помощью / на массив и возвращает массив в replaces(). Заменители () изменяют строки в каждом поле, и только количество элементов в vData заполняет количество полей столбца в строке. Верный? Моя формула выглядит так: {=substitute(substitute(substitute(sbTextToColumn('[SS.xlsm]WS'!$H$840),N/A,NA),I/R,IR), ,)} и это в 5 полях столбца. - person Jim; 24.12.2011
comment
Не совсем то, что я имел в виду. Я предполагал, что вам нужны немедленные результаты других листов в столбцах C. Таким образом, C будет содержать =substitute(substitute(substitute('[SS.xlsm]WS'!$H$840),"N/A","N‌​A"),"I/R","IR")," ","")' E:I` будет содержать {=sbTextToColumn(C2)} Вы можете сделать это за один шаг, но замены должны быть выполнены до разделения, поэтому в этом случае измените sbTextToColumn, чтобы включить заменители. Что-то вроде vData = Split(replace(replace(replace(rng.Value2,"N/A","NA"),"I/A","IA")," ",""), "/") и звонок как {=sbTextToColumn('[SS.xlsm]WS'!$H$840)} - person chris neilsen; 24.12.2011

Этот подход может подойти вам, поскольку он использует свойство Value и не зависит от формул. Он использует функцию Split для разделения данных на одномерный массив. Затем этот массив вставляется в ячейки справа:

Sub sbTextToColumn()
    Dim vData As Variant
    vData = Split(Sheet4.Range("A1").Value, "/")
    Sheet4.Range("B1").Resize(ColumnSize:=UBound(vData) + 1) = vData
End Sub
person Rachel Hettinger    schedule 22.12.2011
comment
хорошо сделано Рэйчел. Для больших наборов данных, возможно, стоит использовать value2, а не value. - person brettdj; 22.12.2011
comment
@rachel, пожалуйста, смотрите дополнение в ОП. Спасибо! Я очень ценю вашу помощь! - person Jim; 23.12.2011