Несколько полезных методов по поиску элементов в ASP.NET и не только...

Думаю каждый, кто разрабатывал и разрабатывает проекты с использованием ASP.NET Web Forms сталкивался с подобной задачей. А дело в том, что стандартный метод, который позволяет получить доступ к элементам непосредственно не доступным напрямую, FindControl() не даёт желаемых результатов, при нескольких уровнях вложенности. Метод не выполняет поиск в иерархии элементов управления, поэтому для поиска чего-либо при использовании нескольких уровней вложенности, он не годится. На самом деле многие начинающие, да и не только, очень часто спотыкаются, когда встречаются с подобным. А решение данной задачи - просто. Вот первый вариант рекурсивного метода поиска элемента с использованием его идентификатора:
public static T FindControlRecursive<T>(Control sourceControl, string targetControlId) 
  where T : class    
{    
  if (sourceControl == null)    
    throw new ArgumentNullException("sourceControl");    
  if (targetControlId == null)    
    throw new ArgumentNullException("targetControlId");    
  if (sourceControl.ID == targetControlId) return sourceControl as T;    
  foreach (Control control in sourceControl.Controls)    
  {    
    T controlToReturn = FindControlRecursive<T>(control, targetControlId);    
    if (controlToReturn != null) return controlToReturn as T;    
  }    
  return null;    
}    
Данный метод найдёт элемент на любом уровне вложенности. Только нужно учитывать то, что на разных уровнях иерархии может быть несколько элементов, с одинаковым ID, поэтому может потребоваться внести ещё и дополнительные критерии помимо идентификарора. Такое часто наблюдается в сложных элементах управления данными, такими как GridView, Repeater и т.п. Ещё один тонкий момент связанный с рекурсией, когда спрашивают, а не может ли подобный метод переполнить стек? Может. Как известно размер стека в .Net ограничен по умолчанию в 1 МБ. В ASP.NET и того меньше, 256 КБ, хотя это значение можно переопределить. Но даже 256 КБ это большое значение, чтобы его переполнить надо сильно постараться. Нужен очень широкий и глубокий уровень вложенности элементов DOM, что кроме как преднамеренно сделать нельзя. Так что, опасность, если можно назвать так, в реальных приложениях не актуальна, если всё правильно реализовано. Вот ещё пара рекурсивных методов для получения всех элементов в иерархии:
public static IEnumerable<Control> SelectAllControls1(this Control sourceControl)
{  
  if (sourceControl == null)
    throw new ArgumentNullException("sourceControl");    
  foreach (Control control in sourceControl.Controls)    
  {    
    yield return control;    
    foreach (Control descendant in control.SelectAllControls1())    
    {    
      yield return descendant;    
    }    
  }    
}
  
public static IEnumerable<Control> SelectAllControls2(this Control sourceControl)  
{ 
  if (sourceControl == null)
    throw new ArgumentNullException("sourceControl"); 
  IEnumerable<Control> controls = sourceControl.Controls.OfType<Control>();  
  return controls.SelectMany(c => SelectAllControls2(c)).Concat(controls);  
}   
И самый последний метод, который собирает элементы уже без рекурсии:
public static IEnumerable<Control> SelectAllControlsIterative(this Control sourceControl)
{    
  if (sourceControl == null)    
    throw new ArgumentNullException("sourceControl");    
  List<Control> result = new List<Control>();    
  Stack<Control> controls = new Stack<Control>();    
  controls.Push(sourceControl);    
  while (controls.Count > 0)    
  {    
    Control current = controls.Pop();    
    ControlCollection childs = current.Controls;    
    result.AddRange(childs.OfType<Control>());    
    foreach (Control item in childs)    
    {    
      controls.Push(item);    
    }    
  }    
  return result;    
} 
Он использует алгоритм поиска в глубину и никакое переполнение ему уже не грозит. Можно было бы использовать поиск в ширину и ещё другие варианты напридумать. Хотя это уже излишне. Думаю этих методов вполне достаточно, для применения их в своих приложениях.



Test576
09.12.2012 15:45
в молодости, я перестал использовать FindControl в тех ситуациях, когда иерархия страницы сложная. потому что его вызов в неловких руках заставляет сервер простроить всю иерархию страницы, если она еще не успела подготовиться.
хорошее иьудобное решение, это использовать Init или Load события нужного вам контрола. вы,блибо возвращаете сендер в глобальную переменную и используете ее позже, либо в этих обработчиках делаете то, что вам нужно.
09.12.2012 15:50
К сожалению, есть случаи при которых без этого не возможно обойтись. Например, при динамических генерациях иерархий элементов.
calabonga
17.12.2012 1:43
Наличие орфографических ошибок (не опечаток, с опечатками в своем блоге хватает мороки) , просто убивает все приятные ощущения от прочтения статей!
17.12.2012 10:23
От ошибок не застрахован никто. Если Вы заметили и она настолько критична, что меняет смысл предложения, скажите и я исправлю.