I want to implement a GridView which takes 3 items in a row, and if the number of items are 2 in last row, then the last row items should be aligned center instead of being left-aligned. Here are a couple of images to explain what I want to achieve.
Currently my implementation looks like
.
And this is what I want to achieve.
Any help would be appreciated.
There are many ways realizing the feature that you mentioned.
To summarize it, you need to inherit GridView and override MeasureOverride ArrangeOverride method to re-calculate each Rect of Panel's children. This way is complex. For more info you could refer to
XAML custom panels overview.
And you could also use PrepareContainerForItemOverride method to re-layout the item directly.
<local:VariableGrid
x:Name="MyGridView"
SelectionMode="Single"
IsSwipeEnabled="False">
<local:VariableGrid.ItemTemplate >
<DataTemplate>
<StackPanel BorderBrush="Red" BorderThickness="3" Height="200" Width="200" Margin="20">
</StackPanel>
</DataTemplate>
</local:VariableGrid.ItemTemplate>
<local:VariableGrid.ItemsPanel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid
Orientation="Horizontal"
VerticalAlignment="Top"
ScrollViewer.HorizontalScrollMode="Enabled"
ScrollViewer.VerticalScrollMode="Disabled"
MaximumRowsOrColumns="4">
</VariableSizedWrapGrid>
</ItemsPanelTemplate>
</local:VariableGrid.ItemsPanel>
</local:VariableGrid>
VariableGrid.cs
public sealed class VariableGrid : GridView
{
public VariableGrid()
{
this.DefaultStyleKey = typeof(VariableGrid);
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
var list = this.ItemsSource as List<string>;
var griditem = element as GridViewItem;
for (var t = ((list.Count - list.Count % 4)); t < list.Count; t++)
{
if (item as string == list[t])
{
if (griditem != null)
{
VariableSizedWrapGrid.SetColumnSpan(griditem, 2);
}
}
}
base.PrepareContainerForItemOverride(element, item);
}
}
However, this simple way can not fit all the scenario.
Related
I want to be able to say:
Get the first textblock, then the first checkbox, both with the number 1 in their name.
Then if the checkbox is checked, then the textblock can be populated.
See code:
for (int i = 1; i < 10; i++)
{
TextBlock a = (this.FindName(string.Format("tb_{0}", i)) as TextBlock);
CheckBox b = (this.FindName(string.Format("ck_{0}", i)) as CheckBox);
if (b.IsChecked.HasValue)
{
if (a != null) a.Text = data.ArrayOfSensors[i].ToString();
}
else
{
if (a != null) a.Text = data.ArrayOfSensors[0].ToString();
}
}
So when the checkbox is enabled, the textblock will be populated with the index from the array.
Many thanks!
EDIT: A slightly better explanation:
The textblocks are named: tb_1, tb_2 etc
The Checkboxes are named: cb_1, cb_2 etc
The array is:
[0] 0
[1] 100
[2] 150
The number is what they all have in common. So I can use a for loop with i as a common variable for each. I also have about 50 textboxes and Comboboxes and don't want to write each one out individually.
EDIT: My ComboBoxes and Textblocks are created on Xaml code like this:
<CheckBox x:Name="Cb_1" Width="15" Height="15" Margin="349,53,127,164" IsChecked="True" />
<TextBlock x:Name="tb_1" Text="80" Height="20" Width="20" Margin="266,35,205,177" />
Its hard to answer without seeing what your XAML looks like, however it sounds like you may be trying to use WPF like it is WinFirms.
To build an interface like this in WPF, you should start by creating a custom class to hold your data, and then use an ItemsControl to render your collection of data.
For example, your class might look something like this
public class SensorData() : INotifyPropertyChanged
{
// should implement INotifyPropertyChanged of course
public string Text { get; set; }
public bool IsChecked { get; set; }
}
And an ObservableCollection<SensorData> might be rendered using an <ItemsControl> with a ItemsPanelTemplate containing both a CheckBox and a TextBox
<ItemsControl ItemsSource="{Binding MyCollectionOfSensorData}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Checked="{Binding IsChecked}" />
<TextBlock Text="{Binding Text}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This will loop through the collection of SensorData objects, and render a CheckBox and TextBox for each one. If you want to do any manipulation of the data from the code-behind, you only need to modify the properties of the SensorData objects.
For example, you could have a loop that goes
for (int i = 0; i < MyCollectionOfSensorData.Length; i++)
{
SensorData item = MyCollectionOfSensorData[i];
if (item.IsChecked)
item.Text = data.ArrayOfSensors[i].ToString();
else
item.Text = "0";
}
And there would be no interaction with the UI objects at all.
I'm building a Windows Store App using the OOTB Grid App template. I'm noticing that there is a 500-1000 millisecond pause when I am navigating from page to page either forward or backward. It is mainly noticeable when I hit the back button (the back arrow stays in the "Hit" state for the 500-1000 ms). Going forward isn't as bad because usually the screen has animations and transitions to fill most of the loading times.
My first thought was that there was something on in the LoadState method that was causing the slowdown, but the only thing I have that isn't from the template I'm running on a background thread and calling it with an async preface.
My second thought was that I was passing a complex object to each page's navigationParameter instead of just passing a simple string. I didn't think this could be the cause since the object should be pass by reference so there really shouldn't be any slowdown because I passed a non-string into the NavigateTo method.
(I haven't read any guidance about this, so I don't know if the page navigation is less snappy when passing non-strings between pages. If anyone has any insight into this, that would be wonderful)
My next thought was that my Xaml is too complex and the pause is the Xaml loading all of the items into the list and what not. This might be the issue and if so, I have no idea how to test or fix it. The UI feels fluid once everything is loaded (all of the items on the page scroll without stutter)
If this is the case, is there any way to show a loading circle with the Xaml generates and then once it is done generating, fade the content in and the circle out?
The main thing that I want to fix is I don't want the back button to "freeze" in the Hit. Any help or guidance would be great!
Basic app info:
Pages have combinations of List and Grid View controls with different Item templates. No images or graphics are used, but I do use a gradient brush on some of the item templates (not super complex, similar to the start screen item gradients). Most lists only have 20-30 items, some more most less.
The average page has 1 Item Source, and 2 Item Display controls, a list and a scroll viewer that holds the details of the selected item.
Details for any item are about 2-3 normal paragraphs of details text and 3-4 < 20 char strings.
EDIT: Project Code:
Page 1 code
protected async override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
if (navigationParameter == null)
this.DefaultViewModel["Groups"] = GlobalData.Catalog.Catalog;
else
this.DefaultViewModel["Groups"] = navigationParameter;
await GlobalData.LibraryDownload.DiscoverActiveDownloadsAsync();
}
The DiscoverActiveDownloadsAsync method is the same code from this example code
SaveState, OnNavigateTo, and OnNavigateFrom methods haven't been modified from the LayoutAwarePage base class.
Page 2 code
protected async override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
if (navigationParameter is CatalogBook)
{
var catBook = (CatalogBook)navigationParameter;
var book = catBook.Book;
await book.InitializeAsync();
this.DefaultViewModel["Group"] = catBook;
this.DefaultViewModel["Items"] = book.Items;
}
else if (navigationParameter is IBook)
{
var book = await Task.Run<IBook>(async () =>
{
var b = (IBook)navigationParameter;
await b.InitializeAsync();
return b;
});
this.DefaultViewModel["Group"] = book;
this.DefaultViewModel["Items"] = book.Chapters;
}
if (pageState == null)
{
// When this is a new page, select the first item automatically unless logical page
// navigation is being used (see the logical page navigation #region below.)
if (!this.UsingLogicalPageNavigation() && this.itemsViewSource.View != null)
{
this.itemsViewSource.View.MoveCurrentToFirst();
}
}
else
{
// Restore the previously saved state associated with this page
if (pageState.ContainsKey("SelectedItem") && this.itemsViewSource.View != null)
{
var number = 0;
if(!int.TryParse(pageState["SelectedItem"].ToString(), out number)) return;
var item = itemsViewSource.View.FirstOrDefault(i => i is ICanon && ((ICanon)i).Number == number);
if (item == null) return;
this.itemsViewSource.View.MoveCurrentTo(item);
itemListView.UpdateLayout();
itemListView.ScrollIntoView(item);
}
}
}
...
protected override void SaveState(Dictionary<String, Object> pageState)
{
if (this.itemsViewSource.View != null)
{
var selectedItem = this.itemsViewSource.View.CurrentItem;
pageState["SelectedItem"] = ((ICanon)selectedItem).Number;
}
}
The InitializeAsync method reads from an SQLite database some of the basic information about a book (chapters, author, etc.) and generally runs very quickly (< 10ms)
Grid code
I get the data by querying an SQLite database using the SQLite-net Nuget Package's async methods. The queries usually look something like this:
public async Task InitializeAsync()
{
var chapters = await _db.DbContext.Table<ChapterDb>().Where(c => c.BookId == Id).OrderBy(c => c.Number).ToListAsync();
Chapters = chapters
.Select(c => new Chapter(_db, c))
.ToArray();
HeaderText = string.Empty;
}
I populate the grids by using the following Xaml:
<CollectionViewSource
x:Name="groupedItemsViewSource"
Source="{Binding Groups}"
IsSourceGrouped="true"
ItemsPath="Items"
d:Source="{Binding DisplayCatalog, Source={d:DesignInstance Type=data:DataCatalog, IsDesignTimeCreatable=True}}"/>
<common:CatalogItemTemplateSelector x:Key="CatalogItemTemplateSelector" />
...
<GridView
Background="{StaticResource ApplicationPageLightBackgroundThemeBrushGradient}"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
SelectionMode="Multiple"
Grid.Row="1"
ItemTemplateSelector="{StaticResource CatalogItemTemplateSelector}"
IsItemClickEnabled="True"
ItemClick="ItemView_ItemClick" Margin="-40,0,0,0">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Height="628" Margin="120,10,0,0" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="1,10,0,6">
<Button
AutomationProperties.Name="Group Title"
Click="Header_Click"
Style="{StaticResource TextPrimaryButtonStyle}" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Margin="3,-7,10,10" Style="{StaticResource GroupHeaderTextStyle}" />
<TextBlock Text="{StaticResource ChevronGlyph}" FontFamily="Segoe UI Symbol" Margin="0,-7,0,10" Style="{StaticResource GroupHeaderTextStyle}"/>
</StackPanel>
</Button>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid Margin="0,0,80,0" ItemHeight="{StaticResource ItemHeight}" ItemWidth="{StaticResource ItemWidth}"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</GridView.GroupStyle>
</GridView>
the CatalogItemTemplateSelector class looks like this:
public class CatalogItemTemplateSelector : DataTemplateSelector
{
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
// cast item to your custom item class
var customItem = item as ICatalogItem;
if (customItem == null)
return null;
string templateName = String.Empty;
if (customItem is CatalogFolder || customItem is CatalogMoreFolder)
{
templateName = "FolderItemDataTemplate";
}
else if (customItem is CatalogBook || customItem is CatalogMoreBook)
{
templateName = "BookItemDataTemplate";
}
object template = null;
// find template in App.xaml
Application.Current.Resources.TryGetValue(templateName, out template);
return template as DataTemplate;
}
}
Both of the templates are ~20 lines of Xaml, nothing special
If there are other pieces of code that I haven't included, let me know and I'll add them.
What does your memory usage look like? Might you be paging to disk?
Excerpt From http://paulstovell.com/blog/wpf-navigation
Page Lifecycles ....
Suppose your page required some kind of parameter data to be passed to it:
... When navigating, if you click
"Back", WPF can't possibly know what values to pass to the
constructor; therefore it must keep the page alive.
... If you navigate passing an object directly, WPF will keep the object alive.
I don't have an answer, but this Channel 9 (Microsoft) video is pretty good regarding XAML performance. Maybe it can help you in your problem.
http://channel9.msdn.com/Events/Build/2012/4-103
The following scenario:
I use a gridview to present grouped data.
I added a TextBlock to the headertemplate which
should contain the number of items in this group. (For example)
( Edit: In my scenario i show always 6 items and want to show the overflow in the TextBlock children of my HeaderTemplate )
How can i access the individual group headers from code to manipulate this TextBlock?
Here is an example of the result:
And here a simplified example of my GroupHeaderTemplate:
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock x:name="overflow"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
So i want to access and manipulate the "overflow" item individually for each generated group!
Here's what you really want.
First edit the GroupStyle HeaderTemplate
<GridView.GroupStyle>
<GroupStyle HidesIfEmpty="True">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Width="500" Margin="5,0,0,5">
<TextBlock HorizontalAlignment="Left">
<Run Text="{Binding Name}" />
<Run FontFamily="Segoe Ui Symbol" Text="" />
</TextBlock>
<TextBlock HorizontalAlignment="Right">
<Run Text="{Binding Children.Count, FallbackValue=0}" />
<Run Text="Items" />
</TextBlock>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid />
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</GridView.GroupStyle>
please note I am using a VaraibleSizedWrapGrid
Next, handle the GridView's Loaded Event
class SubtractConverter : IValueConverter
{
public double Amount { get; set; }
public object Convert(object v, Type t, object p, string l)
{ return System.Convert.ToDouble(v) - Amount; }
public object ConvertBack(object v, Type t, object p, string l)
{ throw new NotImplementedException(); }
}
private void GridView_OnLayoutUpdated(object sender, RoutedEventArgs e)
{
var grid = sender as GridView;
var converter = new SubtractConverter { Amount = 5 * 2 /* padding x2 */ };
foreach (GroupItem group in (grid.ItemsPanelRoot as Panel).Children)
{
var result = VisualTreeHelper.GetChild(group, 0);
while (!(result is Grid))
result = VisualTreeHelper.GetChild(result, 0);
var items = (result as Panel).Children.OfType<ItemsControl>()
.First().ItemsPanelRoot;
var binding = new Binding
{
Path = new PropertyPath("ActualWidth"),
Mode = BindingMode.OneWay,
Converter = converter,
Source = items,
};
var header = (result as Panel).Children.OfType<ContentControl>()
.First().ContentTemplateRoot as FrameworkElement;
header.SetBinding(FrameworkElement.WidthProperty, binding);
}
}
And, presto! Now your header is perfectly sized to the width of the items in the group.
Things to remember (as the designer):
Your grouped items might be as narrow as a single column. Solve this with TextTrimming in the Header TextBoxes.
Your grouped items might be wider than the monitor and # items might be off-screen. Solve this with a MinWidth on the containing grid.
Best of luck!
I finally managed to get my hands on the desired Element by using the VisualTreeHelperExtensions
First of all you have to install XamlToolkit via Nuget
Afterwards add a using directive for the Extensions:
using WinRTXamlToolkit.Controls.Extensions;
Now you can use several more methods on your ui elements , one of them is getDescendantsByType()
which i use to get all textblock elements hold by my gridview. I added a tag to my overflow textblocks which i check for when iterating through the gridviews descendants, see for yourself:
private void ItemsGridView_Loaded(object sender, RoutedEventArgs e)
{
foreach (TextBlock element in this.myGridView.GetDescendantsOfType<TextBlock>())
{
if(element.Tag != null && element.Tag.Equals("itemCountBlock")){
element.Text = "Finally solved!";
}
}
}
This should work out for any ui element and any property one would wanna change.
i have wpf application, a game of sudoku. I have a model and user control for a game grid (9x9 squares). Because my knowledge of binding is limited and i had not enough time, i decided to make it old way without databinding and manualy synchronize model and view. However it is very unclean and synchronization problems appear.
I decided to convert to proper databinding.
I suppose my grid should be something like itemscontrol (listbox or combobox) but instead of linear list it would layout its items into two dimensional grid.
I am new to binding and i have no idea how to achieve my goal. I have model class which has some general info about current puzzle and contains collection of cells. Each cell has its own propertiues like value, possibilities, state etc.
I have user control of entire grid and user control for each individual cell. I need grid to bind to some properties of my model (eg, disabled,enabled,sudoku-type) and each cell to bind to my corresponding cell - value, background etc.
EDIT: Cell are in observable collection. Each cell has X and Y property. I think they should somehow bind to Grid.Row and Grid.Column properties.
Can you please point me some direction how to continue? Especially creating that itemscontrol and how to bind to it?
Thank you.
1) Should it be observable? - no, you could use for example List<List<CellModel>>()
2) If you want to still use array[,] - you may need some kind of converter if you want to use ItemsControl
3) You can use items control with ItemTemplate wich will be an Items control too
hope this will help
Edit 1: Lets create a converter from point 2...
public class ArrayConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var val = value as CellModel[,];
if (val == null) return null;
return ToEnumerable(val);
}
private IEnumerable<IEnumerable<CellModel>> ToEnumerable(CellModel[,] array)
{
var count = array.GetLength(0);
for (int i = 0; i < array.GetLength(0); ++i)
{
yield return GetLine(array, i);
}
}
private IEnumerable<CellModel> GetLine(CellModel[,] array, int line)
{
var count = array.GetLength(1);
for (int i = 0; i < count; ++i)
{
yield return array[line, i];
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Edit 2: Your xaml could look like (see point 3):
<Grid>
<Grid.Resources>
<Converters:ArrayConverter x:Key="ArrayConverter"/>
</Grid.Resources>
<ItemsControl
ItemsSource="{Binding CellArray, Mode=OneWay, Converter={StaticResource ArrayConverter}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl
ItemsSource="{Binding ., Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- TODO: Add cell template here -->
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Edit 3: added line <StackPanel Orientation="Horizontal" IsItemsHost="True"/> - this will make your lines to be rendered horisontal
Edit 4: your view model could now be something like this:
public class GameViewModel : ViewModelBase
{
public void Load()
{
var array = new CellModel[9, 9];
for (int i = 0; i < 9; ++i)
{
for (int j = 0; j < 9; ++j)
{
array[i, j] = new CellModel()
{
//TODO: init properties of the cell[i, j]
};
}
}
this.CellArray = array;
}
CellModel[,] _CellArray;
public CellModel[,] CellArray
{
get
{
return _CellArray;
}
private set
{
if (_CellArray == value) return;
_CellArray = value;
NotifyPropertyChanged("CellArray");
}
}
}
Let me know if something is still unclear
Edit 5: Depends on your last edit, lets change our XAML:
<ItemsControl
ItemsSource="{Binding CellArray, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid
Grid.Column="{Binding X}"
Grid.Row="{Binding Y}">
<!-- TODO: -->
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid IsItemsHost="True">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
You don't need to use an ObservableCollection to bind to a collection, however it is recommended if you want to CollectionChange notification to be automatically implemented.
This means if you update your collection (for example, clearing it and starting a new Game), it will automatically tell the UI that the collection has changed and the UI will redraw itself with the new elements.
You can also Linq statements with ObservableCollections to find a specific element. For example, the following will return the first cell that matches the specified criteria, or null if no item is found
var cell = MyCollection.FirstOrDefault(
cell => cell.Y == desiredRowIndex && cell.X == desiredColumnIndex);
I have a Datatemplete for List-box Item in which I have a Grid with two columns using WPF. In the first column I want to put few customized controls(Buttons) dynamically using C# in code behind. I don't know how to start and from where should I start, can anybody please help me with some great inputs and examples. Any answer will be greatly appreciate.
Thanks in advance.
XAML code:
<ListBox x:Name="ListBoxItem"
Grid.Row="1"
SelectionMode="Extended"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
FocusVisualStyle="{x:Null}"
KeyboardNavigation.IsTabStop="False"
Background="DarkGray"
ItemsSource="{Binding}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel x:Name="ListContent"
IsItemsHost="True"
Width="500"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel LastChildFill="True"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<StackPanel DockPanel.Dock="Left"
Width="30"
Height="{Binding Height}">
<--Here I want to put few customize buttons in code behind-->
</StackPanel>
<Image x:Name="MainPage"
Stretch="UniformToFill"
Source="{Binding ImagePath}"
Height="{Binding Height}"
Width="{Binding Width}"/>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
You specified wanting to use code behind, so it would look something like this:
XAML:
<StackPanel Initialized="StackPanel_Initialized" .. />
Code behind:
using MyNamespace;
private void StackPanel_Initialized(object sender, EventArgs e)
{
MyControl newItem = new MyControl();
// Set any other properties
StackPanel parent = sender as StackPanel;
parent.Children.Add(newItem);
}
If you are looking for adding Controls inside a the First column of your grid then put a Panel inside the first column and in code behind add controls as child to that Panel. So as you mentioned in above that you are using DataTemplete then I would like to say that you can access that Panel something like:
Put the below codes inside the event where you wnt to add the controls.
ListBoxItem item = (ListBoxItem)(this.lst.ItemContainerGenerator.ContainerFromIndex(i));
ContentPresenter presenter = FindVisualChild<ContentPresenter>(item);
DataTemplate template = presenter.ContentTemplate;
StackPanel stack = (StackPanel)template.FindName("FirstColumn Panel Name", presenter);
and then call the below method:
private childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
return (childItem)child;
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}