Okay, i feel slightly dumb for askin this but, I have a listview with a templated class MyClass or whatever, whenever i "myListView.Add(new MyClass())" the winrt platform adds a new UIElement there and binds the proper properties into their proper uielements properly, now, I want to be able to iterate through these logical items (myListView.Items or myListView.SelectedItems) and get their corresponding UIElement for animation, is that possible?
like for example
class PhoneBookEntry {
public String Name { get;set }
public String Phone { get;set }
public PhoneBookEntry(String name, String phone) {
Name = name; Phone = phone;
}
};
myListView.Add(new PhoneBookEntry("Schwarzeneger", "123412341234");
myListView.Add(new PhoneBookEntry("Stallone", "432143214321");
myListView.Add(new PhoneBookEntry("Statham", "567856785678");
myListView.Add(new PhoneBookEntry("Norris", "666666666666");
And in XAML (just an example so i can explain what I mean)
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Phone}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
So, my point and objective here is to
foreach(PhoneBookEntry pbe in myListView.Items) // or SelectedItems
{
UIElement el; // How can I get the UIElement associated to this PhoneBookEntry pbe?
if(el.Projection == null)
el.Projection = new PlaneProjection;
PlaneProjection pp = el.Projection as PlaneProjection;
// Animation code goes here.
if(myListView.SelectedItems.Contains(pbe)
//something for selected
else
//something for not selected
}
I just need a way to get an UIElement which is being used to represent this logical data class PhoneBookEntry in the templated listview.
Also, this necessity comes with a very big problem I'm having where, selected items doesn't differ visually on Windows Phone -_- any ideas?
You can also use the ListView.ContainerFromItem or ListView.ContainerFromIndex methods which will return the container UI element for a given item in the list view (of course, only if the container is generated)
Ok I may look like a fool answering my own question but i've figured a way out.
First things first: ListViews only create UIElements for determinate items in the list (the ones cached and the ones being shown). So if you do add 2000 items to myListView.Items, the effective ammount of UIElements representing these items will be 56 or close number.
Because, the ItemListView simulates the UIElements even if they're not there, just to give size and position to the scrollbar (hence why scrolling down on very large lists cause some lag, WinRT is unloading UIElements and loading new ones)
From that, I figured out I could simply iterate through the current list of loaded UIElements through
// For each of the cached elements
foreach(LIstViewItem lvi in myListView.ItemsPanelRoot.Children)
{
// Inside here I can get the base object used to fill the data template using:
PhoneBookEntry pbe = lvi.Content as PhoneBookEntry;
if(pbe.Name == "Norris")
BeAfraid();
// Or check if this ListViewItem is or not selected:
bool isLviSelected = lvi.IsSelected;
// Or, like I wanted to, get an UIElement to animate projection
UIElement el = lvi as UIElement;
if(el.Projection == null)
el.Projection = new PlaneProjection();
PlaneProjection pp = el.Projection as PlaneProjection;
// Now I can use pp to rotate, move and whatever with this UIElement.
}
So, this is it. Right beneath my nose...
Related
I've got a listview that has, as a datatemplate, a relatively complex UserControl, with a couple properties that are being binded to it.
The ItemsPanel is the following:
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel VerticalAlignment="Bottom" ItemsUpdatingScrollMode="KeepLastItemInView"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
My listview generally contains about 70 items, however I'm running into quite a major issue:
Every time I scroll up about 30 items and then go back down, the properties seem to have gotten messed up, and some of them seem to have switched with the properties of items higher up.
For example, if before I had the following:
property A = 1
property A = 2
property A = 3
After scrolling up and back down I would have:
property A = 1
property A = 2
property A = 1
How can I ensure that the items either keep their properties or reload them correctly?
I finally figured out how to fix this: If anyone else is running into a similar issue, it's potentially because you're handling the PropertyChanged event without resetting properties that are missing or are null. For example, if this is the usercontrol inside the datatemplate of a listview:
<local:CustomControl CustomText={Binding text}/>
And it is being handled like this:
public string CustomText
{
get { return (string)GetValue(CustomTextProperty); }
set { SetValue(CustomTextProperty, value); }
}
public static readonly DependencyProperty CustomTextProperty = DependencyProperty.Register(
nameof(CustomText),
typeof(string),
typeof(CustomControl),
new PropertyMetadata(string.Empty, OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as CustomControl;
if(instance.CustomText != "")
myTextBlock.Text = instance.CustomText;
}
Every time that virtualization recycles a CustomControl, if ever it is being binded to an empty string, because of the if(instance.CustomText != "") the textblock will not get updated, and old properties will show up again. The same will happen if OnPropertyChanged never gets called for some reason.
It seems kind of stupid in this particular situation, but it can get quite confusing on large usercontrols with a lot of properties and subproperties
I am trying to use a basic implementation of "FindVisualChild" for WPF in order to find a specific Grid that exists within a DataTemplate of a ListBox.
The implementation is as follows:
private DependencyObject FindVisualChild<T>(DependencyObject obj, string name)
{
Console.WriteLine(((FrameworkElement)obj).Name);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
FrameworkElement fe = child as FrameworkElement;
//not a framework element or is null
if (fe == null) return null;
if(!string.IsNullOrEmpty(fe.Name))
Console.WriteLine(fe.Name);
if (child is T && fe.Name.Equals(name, StringComparison.CurrentCultureIgnoreCase))
return child;
else
{
//Not found it - search children
DependencyObject nextLevel = FindVisualChild<T>(child, name);
if (nextLevel != null)
return nextLevel;
}
}
return null;
}
My issue is that this code was working yesterday to find a Grid that I have defined in the DataTemplate with the name "MainTermServListGrid" as shown here:
<ListBox HorizontalAlignment="Stretch" Grid.Row="1" x:Name="TermServListBox" ItemsSource="{Binding TermServs}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="MainTermServListGrid">
//code here
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
However, today when I try to use the same method to find that Grid, the result is always null. If I debug and step through the code, it looks like it is not even finding any of the items that exist within the DataTemplate.
I am calling the FindVisualChild method right after I populate the ListBox with items. Could it be that I am not waiting long enough and the window does not have enough time to finish initializing and presenting the new items in the list box before I am trying to find a specific child within that listbox?
If that is the case, would a simple call to await Task.Delay(500) work to give the UI enough time to finish loading? Or am I doing something totally wrong here?
Turns out I was right in assuming I was not giving the UI enough time to finish loading. I believe it is due to the fact that I was populating the ListBox with items using a particular method, and at the end of that method I was raising an Event which triggered the search in the Window's code-behind.
Because there was never a period if time between finishing the loading of the items and the event being raised to look for them, I dont think the ui had time to finish initializing everything.
Basically all I did to fix the issue in my event handler was the following:
private async void ViewModelOnListPopulated(object sender, EventArgs eventArgs)
{
await Task.Delay(500);
//Continue on to find the visual child...
}
I have a UserControl contained in a class When some conditions are met I initialize the UserControl and I add it in a Grid, setting row and column property depending on the position of the class in a list. When I change that position, I need to update the position of the element on the Grid, but since not every class has its UserControl initialised and added, I need to check when I order the elements in the Grid.
To check if the UserControl is initialised I check if it is null
classElement.userControl == null
How do I check if the UserControl has been added to the Grid?
Perhaps the easiest way to achieve your requirements would be for you to simply iterate through the children of the Grid using the Children property, checking each one in turn. You could do something like this:
<Grid Name="YourGrid">
...
</Grid>
...
foreach (UIElement uiElement in YourGrid.Children)
{
if (uiElement.GetType() == typeof(UserControl))
{
if (uiElement != null)
{
// Do something with your control here
}
}
}
UPDATE >>>
I don't understand your comment... you said I need a way to know if the usercontrol is in the grid or not... that is exactly what I have provided you with. If you are adding the UserControl to the Grid, then you must have a reference to the Grid. If you have a reference to the Grid, then you can iterate the controls that have been added to that Grid just as I have shown you. If you are adding the UserControl from the UserControl code behind, then you can do this:
foreach (UIElement uiElement in YourGrid.Children)
{
if (uiElement.GetType() == typeof(UserControl))
{
if (uiElement != null)
{
if (uiElement == this)
{
// this UserControl is in the Grid
}
}
}
}
If this does not solve you problem, then please take the time to provide a decent description of your problem that would enable me to suggest a fix for it. From what I understand presently, this is your fix.
We have a user control with a custom dependency property (DP). The DP is bound to an ObservableCollection.
When a new item is added to the collection programatically, the databinding does not update the target DP. Why? We think it's because, unfortunately, in our case the target is not a ListBox or ListView, but a Canvas. The DP, when changed or initialized, is supposed to draw a Shape (!) onto the Canvas, and the shape's position and size is bound to the collection item's two properties: WIDTH, LEFT.
Ideally we don't want to clear the Canvas and redraw all items just becasue one has been added (or deleted). But how?
So:
How can the custom DP take care of drawing the shape for the new collection item? What callback do we need, at what point in time does this have to happen, and what specific MetaDataOptions might there?
Also, are there any good resources out there concerning all these dependency property options. They are quite confusing. MSDN does not really help with what we're trying to do.
Thanks!
EDIT:
The ObservableCollection is like so:
public class Projects : ObservableCollection<Project>
{
//no ommitted code. this class really IS empty!
}
The DP is like so:
public class MyUserControl : UserContorl
{
public static readonly DependencyProperty... etc. typeof(Projects)
private static void OnProjectsChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyUserControl u = d as MyUserControl;
CpProjectCollection projects = e.NewValue as CpProjectCollection;
u.Refresh(projects);
}
private void Refresh(CpProjectCollection projects)
{
foreach (CpProject p in projects)
{
//...add each project to the Canvas
ProjectContorl pc = new ProjectControl();
pc.Project = project;
Binding b = new Binding("StartTime");
b.Converter = new TimeSpanConverter();
b.Source = pc.Project;
b.Mode = BindingMode.TwoWay;
c.SetBinding(Canvas.LeftProperty, b);
//do the same for project duration
}
}
}
If you bind to ObservableCollection, you get the change notification if the collection is replaced with another collection, not when the collection's content is changed. So, you'll need to subscribe to CollectionChanged event in your code-behind.
If you subscribe to CollectionChanged, you can see which are the new/deleted items in your ObservableCollection. You can add a new shape for each new item and remove old shapes for deleted items.
How can I tell my TabControl to set the focus to its first TabItem, something like this:
PSEUDO-CODE:
((TabItem)(MainTabControl.Children[0])).SetFocus();
How about this?
MainTabControl.SelectedIndex = 0;
this.tabControl1.SelectedTab = this.tabControl1.TabPages["tSummary"];
I've found it's usually a best practice to name your tabs and access it via the name so that if/when other people (or you) add to or subtact tabs as part of updating, you don't have to go through your code and find and fix all those "hard coded" indexes. hope this helps.
I realise this was answered a long time ago, however a better solution would be to bind your items to a collection in your model and expose a property that selected item is bound to.
XAML:
<!-- MyTemplateForItem represents your template -->
<TabControl ItemsSource="{Binding MyCollectionOfItems}"
SelectedItem="{Binding SelectedItem}"
ContentTemplate="{StaticResource MyTemplateForItem}">
</TabControl>
Code Behind:
public ObservableCollection<MyItem> MyCollectionOfItems {
get;
private set;
}
private MyItem selectedItem;
public MyItem SelectedItem{
get { return selectedItem; }
set {
if (!Object.Equals(selectedItem, value)) {
selectedItem = value;
// Ensure you implement System.ComponentModel.INotifyPropertyChanged
OnNotifyPropertyChanged("SelectedItem");
}
}
}
Now, all you have to do to set the item is:
MyItem = someItemToSelect;
You can use the same logic with the SelectedIndex property, further, you can use the two at the same time.
This approach allows you to separate your model correctly from the UI, which could allow you to replace the TabControl with something else down the line but not requiring you to change your underlying model.
Look at the properties for the tab control...
Expand the TabPages properties "collection"...
Make note of the names you gave the members.
ie. a tab control called tabMain with 2 tabs called tabHeader and tabDetail
Then to select either tab...You have to set it with the tabname
tabMain.SelectedTab = tabHeader;
tabControl1.SelectedTab = item;
item.Focus();
Basically all of the answers here deal with SELECTION, which does not answer the question.
Maybe that is what OP wanted, but the question very specifically asks for FOCUS.
TabItem item = (TabItem)MainTabControl.Items[0];
// OR
TabItem item = (TabItem)MainTabControl.SelectedItem;
// Then
item.Focus();
tabControl.SelectedItem = tabControl.Items[0];
If you have a Tabcontroller named tabControl you could set the selectedIndex from different methods, i use following methods mostly.
codebehind:
tabControl.SelectedIndex = 0; // Sets the focus to first tabpanel
clientside:
First, put the following javascript in your aspx/ascx file:
<script type="text/javascript">
function SetActiveTab(tabControl, activeTabIndex) {
var activeTab = tabControl.GetTab(activeTabIndex);
if(activeTab != null)
tabControl.SetActiveTab(activeTab);
}</script>
Then add following clientside event to prefered controller:
OnClientClick="function(s, e) { SetActiveTab(tabControl, 0);
it's better to use the following type of code to select the particular
item in the particular tab...
.
private void PutFocusOnControl(Control element)
{
if (element != null)
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input,
(System.Threading.ThreadStart)delegate
{
element.Focus();
});
}
And in calling time... tabcontrol.isselected=true;
PutFocusOnControl(textbox1);
will works fine...
Private Sub TabControl1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles TabControl1.SelectedIndexChanged
'MsgBox(TabControl1.SelectedIndex)
If TabControl1.SelectedIndex = 0 Then
txt_apclntFrstName.Select()
Else
txtApplcnNo.Select()
End If
End Sub
It worked for me to set focus to the last tab just after I open it:
//this is my assignment of the collection to the tab control
DictTabControl.DataContext = appTabs.DictTabs;
//set the selected item to the last in the collection, i.e., the one I just added to the end.
DictTabControl.SelectedItem = DictTabControl.Items[(DictTabControl.Items.Count-1)];