Below is the markup that generates a list of buttons.
<ItemsControl x:Name="Items" Grid.Row="5" Grid.ColumnSpan="2">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Content="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
There is filtering applied to the buttons using a filtring criteria
collectionView = (CollectionView)CollectionViewSource.GetDefaultView(Items.ItemsSource);
collectionView .Filter = FilterList;
The problem is that i want to retain states of button checked when i toggle the filter state. I have tried subscribing to the event StatusChanged
Items.ItemContainerGenerator.StatusChanged += new System.EventHandler(ItemContainerGenerator_StatusChanged);
but the controls dont seem to be generated at the point when status is ContainersGenerated
void ItemContainerGenerator_StatusChanged(object sender, System.EventArgs e)
{
if (Items.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
RefreshButtons();
}
}
The only way to access them is to use the VisualTree.
Just use something like this:
public static T[] FindVisualChilds<T>(DependencyObject parent, Func<DependencyObject, bool> CompareDelegate)
where T : DependencyObject
{
if (VisualTreeHelper.GetChildrenCount(parent) == 0) return null;
List<T> childs = new List<T>();
if (CompareDelegate(parent))
childs.Add(parent as T);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var tmp = FindVisualChilds<T>(VisualTreeHelper.GetChild(parent, i), CompareDelegate);
if (tmp != null)
childs.AddRange(tmp);
}
return childs.ToArray();
}
Now pass as firstparameter your datagrid and as second a delegate that checks if the control is the control you want. As result you will get all control those are found
Related
I am trying to show the user a bunch of data based on a grid (not the ui element grid). The data changes and the displayed instances are based on the X/Y position of that data. I used an example I found here (but can't find it right now) and I basically got everything working. Except that the displayed grid is on it's side. I have created a test project under which I'm trying to make the thing work correctly before taking that to my main project.
Here is what it looks like
Here is what I want it to look like (Tanks Paint)
Codebehind: (Quick and dirty but works for testing purposes)
private DataContainer[][] dataArray;
public MainWindow()
{
dataArray = new DataContainer[3][];
dataArray[0] = new DataContainer[2];
dataArray[1] = new DataContainer[2];
dataArray[2] = new DataContainer[2];
dataArray[0][0] = new DataContainer(1,"At: 0,0");
dataArray[1][0] = new DataContainer(2, "At: 1,0");
dataArray[2][0] = new DataContainer(3, "At: 2,0");
dataArray[0][1] = new DataContainer(4, "At: 0,1");
dataArray[1][1] = new DataContainer(5, "At: 1,1");
dataArray[2][1] = new DataContainer(6, "At: 2,1");
InitializeComponent();
lst.ItemsSource = dataArray;
}
XAML:
<Window.Resources>
<DataTemplate x:Key="DataTemplate_Level2">
<Border Name="border" BorderBrush="LightGreen" BorderThickness="5"
Padding="2" Margin="2" Width="80">
<Grid Margin="2">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Border Name="border2" BorderBrush="Red" BorderThickness="2"
Grid.Row ="0" Padding="1" Margin="1">
<TextBlock Text="{Binding Path=Text1, UpdateSourceTrigger=PropertyChanged}"/>
</Border>
<Border Name="border3" BorderBrush="Blue" BorderThickness="2"
Grid.Row ="1" Padding="1" Margin="1">
<TextBlock Text="{Binding Path=Number1, UpdateSourceTrigger=PropertyChanged}"/>
</Border>
</Grid>
</Border>
</DataTemplate>
<DataTemplate x:Key="DataTemplate_Level1">
<ItemsControl ItemsSource="{Binding}" ItemTemplate="{DynamicResource DataTemplate_Level2}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl x:Name="lst" ItemTemplate="{DynamicResource DataTemplate_Level1}"/>
</Grid>
What I've tried:
I tried changing stackpanel orientation to Vertical. That got me half way there. The Result is left column on top of right column. The problem is changing the 3 elements within that column to be Horizontal, which should give the result I'm looking for.
I also triend fondling the DataTemplate_Level2 but that just left me with error messages.
To be honest, I'm new to WPF (in case you haven't noticed) This databinding and templating is the most complicated UI part of my project and I've been thoroughly confused by trying to learn all the stuff at once. I would appreciate it if someone would point me towards an answer here. It's almost Christmas and I would much rather not spend the holidays thinking about this problem.
One solution I can think of is swapping the X and Y coordinates in the array to be dataArray[Y][X] instead of dataArray[X][Y] but that would make things difficult for me in the future and it would be best to get the problems solved now.
In case you wonder what the datacontainer object looks like, but this shouldn't be important to the solution of this issue. it's just something I whipped together to demonstrate that I have gotten the binding and updating to work. It just displays the coordinates it should be at and at random interval changes the value of the number:
public class DataContainer : INotifyPropertyChanged
{
private int number1 = 0;
private string text1 = "none";
private System.Timers.Timer aTimer;
public event PropertyChangedEventHandler PropertyChanged;
public DataContainer(int num,string str)
{
number1 = num;
text1 = str;
aTimer = new System.Timers.Timer(num*1000);
aTimer.Elapsed += OnTimedEvent;
aTimer.AutoReset = true;
aTimer.Enabled = true;
}
private void OnTimedEvent(object sender, ElapsedEventArgs e)
{
Random r = new Random();
number1 = r.Next(0, 100);
number1 += 1;
this.NotifyPropertyChanged("Number1");
}
public int Number1
{
get { return this.number1; }
set
{
if (this.number1 != value)
{
this.number1 = value;
this.NotifyPropertyChanged("Number1");
}
}
}
public string Text1
{
get { return this.text1; }
set
{
if (this.text1 != value)
{
this.text1 = value;
this.NotifyPropertyChanged("Text1");
}
}
}
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public MainWindow()
{
InitializeComponent();
lst.ItemsSource = new List<DataContainer>
{
new DataContainer(1, "At: 0,0"),
new DataContainer(2, "At: 1,0"),
new DataContainer(3, "At: 2,0"),
new DataContainer(4, "At: 0,1"),
new DataContainer(5, "At: 1,1"),
new DataContainer(6, "At: 2,1")
}
}
<ItemsControl x:Name="lst" ItemTemplate="{DynamicResource DataTemplate_Level2}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformsGrid Columns="3" Rows="2"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
I had some sleep and tested out some things and figured out where exactly things are going wrong by adding more borders. Here's the solution that I found myself:
<ItemsControl x:Name="lst" ItemTemplate="{DynamicResource DataTemplate_Level1}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
and the data template:
<DataTemplate x:Key="DataTemplate_Level1">
<ItemsControl ItemsSource="{Binding}" ItemTemplate="{DynamicResource DataTemplate_Level2}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
like the title said i want to get the value of a property from a selected listboxitem on a button click
<ListBox x:Name="IconListbox" Background="{x:Null}" BorderBrush="Black">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBoxItem x:Name="defaultIcon">
<Grid Background="Black">
<Border BorderBrush="#FF1EF3F3" BorderThickness="2">
<Image x:Name="defaultIconImage" Width="50" Height="50" Source="icon.png"/>
</Border>
</Grid>
</ListBoxItem>
<ListBoxItem>
<Grid Background="Black">
<Border BorderBrush="#FF1EF3F3" BorderThickness="2">
<Image x:Name="secondIconImage" Width="50" Height="50" Source="SecondIcon.png"/>
</Border>
</Grid>
</ListBoxItem>
</ListBox>
For example if i click the button it should return the image source of the current selected item. So if ListboxItem defaultIcon is selected it should return defaulticon.png. How can i do this ?
Edit:
Maybe i am taking the wrong aproach by trying to use a listbox. I'm verry new to Xaml code and i'll try to better explain what i want as a result.
Here is a picture that i will use to try and explain: Image
So what i want is when 1 is selected i need it to return the source of the blue flame image when i click the save button
when 2 is selected i need it to return the source of the blue facebook image when i click the save button
You can find the Image inside SelectedItem like the code below
var selectedItem = IconListbox.SelectedItem as ListBoxItem;
if (selectedItem != null)
{
var image = selectedItem.GetChildOfType<Image>();
if (image != null)
{
var source = image.Source;
}
}
extension method to get child of specific type
public static T GetChildOfType<T>(this DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
IconListbox.SelectedItem and then cast it or create a new object of them.
Example
Image i = (Image)IconListbox.SelectedItem;
I have this XAML:
<ItemsControl x:Name="recentSearches"
Margin="0,65,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding q}"
TextWrapping="Wrap"
Foreground="AliceBlue"
Padding="2,6,2,2"
Margin="12,-6,12,0"
FontSize="20" />
</DataTemplate>
</ItemsControl.ItemTemplate>
and this code behind:
private void showLatestSearches()
{
if (fmn.checkLatestSearchesExtistence())
{
List<RecentSearchItem> recent = new List<RecentSearchItem>();
List<String> l = fmn.readLatestSearches();
for (int i = 0; i <= l.Count-1; i += 1)
{
RecentSearchItem r = new RecentSearchItem();
r.q = l[i];
r.generalbg = grau;
recent.Add(r);
}
recentSearches.DataContext = recent;
}
}
the object called fmn reads a .txt from the isolated storage.
But why doesn't anything show up with this StackPanel?
ItemsControl.ItemsSource has to be bound to a collection, for notifications the best would be ObservableCollection<T>.
You are setting the DataContext at the last possible minute, a better way would be to set
DataContext to a ViewModel, could be place where you create your View.
public class Form :UserControl
{
DataContext = new YourViewModel() ;
}
In XAML:
ItemsSource="{Binding SearchesCollection}"
SearchesCollection would be a property in YourViewModel of type ObservableCollection<string>. Whenever you add a new item to SearchesCollection the View updates.
This Databinding Tutorial should help.
Thanks to Lews Therin I managed to finally bind my data to the stackpanel:
<ItemsControl x:Name="recentSearches"
ItemsSource="{Binding recent}"
Background="{Binding generalbg}"
Margin="0,65,0,0" Tap="recentSearches_Tap">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding q}"
Foreground="{Binding foreground}"
TextWrapping="Wrap"
Padding="2,6,2,2"
Margin="12,-6,12,0"
FontSize="20" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
and the code behind:
private void showLatestSearches()
{
if (fmn.checkLatestSearchesExtistence())
{
List<RecentSearchItem> recent = new List<RecentSearchItem>();
List<String> l = fmn.readLatestSearches();
for (int i = 0; i <= l.Count-1; i += 1)
{
RecentSearchItem r = new RecentSearchItem();
r.q = l[i];
r.generalbg = grau;
r.foreground = blau;
recent.Add(r);
}
recentSearches.ItemsSource = recent;
}
}
this works, but unfortunately there seems to be no way to determine, which TextBox is tapped, when one is tapped.
I am looking to implement a treeview inside a combobox. Basically I want it to show as a combobox when collapsed but a treeview inside combo box when expanded. When a user clicks on a node, I want it to show in the collapsed combo box. I have got this working so far.
The problem I am having is that how do I show a default value from c# when this combo box is loaded. Please help guys as I am running out of ideas :)
Thanks in advance.
Data Template
<DataTemplate x:Key="TreeViewExpanded" >
<StackPanel>
<TreeView x:Name="DPointTree" Margin="5" ItemsSource="{Binding Datapoint}"
Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBox}}}"
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Field}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding = "{Binding Path=SelectedFieldName, Mode=TwoWay}" Value = "">
<Setter Property = "Visibility" Value = "Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Here is the Xaml
<ComboBox Name="cmbFieldName" Width="150" Background="White" ItemsSource="{Binding}" SelectedItem="{Binding SelectedFieldName , Mode=TwoWay}" >
<ComboBox.ItemTemplateSelector>
<local:TreeViewSelector/>
</ComboBox.ItemTemplateSelector>
</ComboBox>
Here is the DataSet passed to this.
Datapoint _datapoint2 = new Datapoint();
_datapoint2.Name = "Alpha";
_datapoint2.FieldID.Add("Contains Elements");
_datapoint2.Field.Add("1 Year");
_datapoint2.Field.Add("2 Year");
_datapoint2.Field.Add("3 Year");
Try this:
private void TreeView_Loaded(object sender, RoutedEventArgs e)
{
TreeView areaTreeView = sender as TreeView;
// Expand the treeview
if (areaTreeView != null)
{
ExpandCollapseTreeNodes(areaTreeView, true);
}
}
public static void ExpandCollapseTreeNodes(ItemsControl parentContainer, bool isExpanded)
{
foreach (Object item in parentContainer.Items)
{
TreeViewItem currentContainer = parentContainer.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (currentContainer != null) // && currentContainer.Items.Count > 0
{
//expand the item
currentContainer.IsExpanded = isExpanded;
//if the item's children are not generated, they must be expanded
if (isExpanded && currentContainer.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
{
//store the event handler in a variable so we can remove it (in the handler itself)
EventHandler eh = null;
eh = new EventHandler(delegate
{
//once the children have been generated, expand those children's children then remove the event handler
if (currentContainer.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
ExpandCollapseTreeNodes(currentContainer, isExpanded);
currentContainer.ItemContainerGenerator.StatusChanged -= eh;
}
});
currentContainer.ItemContainerGenerator.StatusChanged += eh;
}
//otherwise the children have already been generated, so we can now expand those children
else
{
ExpandCollapseTreeNodes(currentContainer, isExpanded);
}
}
}
}
it is not pretty but works to select a node in the tree
// in code behind after the tree is declared, e.g. after InitializeComponent();
SynchronizationContext.Current.Post(delegate
{
// expand the tree to the selected node after loading
treeView.ExpandAll(); // or if path is known treeView.ExpandPath(...);
SynchronizationContext.Current.Post(delegate
{
treeView.GetContainerFromItem(root.Children[0]).IsSelected = true;
}, null);
}, null);
I have a datatemplate which contains a grid and inside the grid I have a combobox.
<DataTemplate x:Key="ShowAsExpanded">
<Grid>
<ComboBox Name ="myCombo" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="5"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource MyItems}">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
</Grid>
</DataTemplate>
I then I have a grid that refers to that template through styling.
<Grid>
<ContentPresenter Name="_contentPresenter" Style="{DynamicResource StyleWithCollapse}" Content="{Binding}" />
</Grid>
How can I access through code behing the myCombo to basically set its DataContext?
Three ways which I know of.
1.Use FindName
ComboBox myCombo =
_contentPresenter.ContentTemplate.FindName("myCombo",
_contentPresenter) as ComboBox;
2.Add the Loaded event to the ComboBox and access it from there
<ComboBox Name ="myCombo" Loaded="myCombo_Loaded" ...
private void myCombo_Loaded(object sender, RoutedEventArgs e)
{
ComboBox myCombo = sender as ComboBox;
// Do things..
}
3.Find it in the Visual Tree
private void SomeMethod()
{
ComboBox myCombo = GetVisualChild<ComboBox>(_contentPresenter);
}
private T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
First of all, I can't even find the relation between the Resource (ShowAsExpanded) and the usage inside the ContentPresenter. But for the moment, let's assume that the DynamicResource should point to ShowAsExpanded.
You can't and shouldn't access the combobox via code. You should bind the datacontext to the grid that uses the style. If you don't want to do that, you will have to find the content at runtime and search for the child combobox.
you need to use FindName. check out http://msdn.microsoft.com/en-us/library/system.windows.frameworktemplate.findname.aspx