wpf treeview blues. I want to select an item - c#

I'm trying to select a TreeViewItem. Now, I have access to the containing TreeViewItem and have told it to expand so I can select its kid. If it's already expanded all is well, if it's not then I run this code:
EventHandler selector = new EventHandler(delegate
{
if (selectedDirectoryTreeItem.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
TreeViewItem want = selectedDirectoryTreeItem.ItemContainerGenerator.ContainerFromItem(dirWeWantSelected) as TreeViewItem;
if (want == null)
return;
want.IsSelected = true;
// selectedDirectoryTreeItem.ItemContainerGenerator.StatusChanged -= selector;
}
});
selectedDirectoryTreeItem.ItemContainerGenerator.StatusChanged += selector;
So my question is, why wont it select? want is always null. I'm scouring the interwebs looking for another way of doing this but it would be cool if somebody could explain this to me

I've personally always found it easiest to stick a Selected property into my model object and then just bind the TreeViewItem Selected property to the Selected property of the model. Here is some code:
Model
public class Data : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Data()
{
DataItems = new List<Data>();
}
public string Name { get; set; }
private bool _selected;
public bool Selected
{
get { return _selected; }
set
{
_selected = value;
OnPropertyChanged("Selected");
}
}
public List<Data> DataItems { get; set; }
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:controls="clr-namespace:MyControls;assembly=MyControls"
Title="Window1">
<Window.Resources>
<Style x:Key="CustomTreeViewItem" TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding Path=Selected, Mode=TwoWay}" />
<Setter Property="IsExpanded" Value="True" />
</Style>
</Window.Resources>
<DockPanel Background="Transparent">
<TreeView x:Name="_tvTest" DockPanel.Dock="Left" ItemContainerStyle="{StaticResource CustomTreeViewItem}" Width="300" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Data}" ItemsSource="{Binding DataItems}">
<TextBlock Text="{Binding Name}" Padding="2" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Padding="2" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<Button Content="Select Random TreeView Item" Click="Button_Click" Height="50" Width="200" />
</DockPanel>
</Window>
Code behind
public partial class Window1 : Window
{
private Random _random;
private List<Data> _dataItems;
public Window1()
{
InitializeComponent();
_dataItems = Init();
_tvTest.ItemsSource = _dataItems;
_random = new Random(5);
}
private List<Data> Init()
{
List<Data> dataItems = new List<Data>();
for (int i = 1; i <= 10; i++)
{
Data d1 = new Data();
d1.Name = "Data:" + i.ToString();
for (int j = 1; j <= 4; j++)
{
Data d2 = new Data();
d2.Name = "Data:" + i.ToString() + j.ToString();
d1.DataItems.Add(d2);
}
dataItems.Add(d1);
}
return dataItems;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
int index = _random.Next(0, 9);
int subIndex = _random.Next(0, 3);
if (subIndex == 0)
_dataItems[index].Selected = true;
else
_dataItems[index].DataItems[subIndex - 1].Selected = true;
}
}

In my opinion, this is a bug in WPF, but I have run into the same issue several times. I never trust the ItemContainerGenerator's Status and instead loop on separate thread as such:
private void _selectTreeViewHelper(object directory) {
TreeViewItem want = null;
bool broke = false; //probably some sort of wait timeout instead, but works for sake of example
while (true) {
Dispatcher.Invoke(new Action(delegate {
want = selectedDirectoryTreeItem.ItemContainerGenerator.ContainerFromItem(directory) as TreeViewItem;
if (want != null && want.IsLoaded) {
want.IsSelected = true;
broke = true;
}
}));
if (broke) { break; }
Thread.Sleep(100);
}
}
Then call it:
var thread = new Thread(new ParameterizedThreadStart(_selectTreeViewHelper));
thread.Start(dirWeWantSelected);
Pain I know, but it works.

Thanks for the help, I figured it out. Well it works anyway but i'm not entirely sure why it didn't before... if anyone can tell me it would be lovely... I kinda combined elements of the two responses above and then added on a little bit to make it work. For some reason, no matter what i do I cant select the TVI (TreeViewElement) if its parent wasn't already expanded, even if the parent TVI said its contents were generated and, in fact, had contents i could iterate through. My solution was thus to wait for the contents to be generated and then itrate through them and find the one i wanted. It's really weird to me that I couldn't just grab a container given its contents. Meh. My code could stand to be refactored a little bit but here it is: (works perfectly)
public void listItemClickClick(object sender, RoutedEventArgs e)
{
try
{
UserFile fil = (UserFile)(sender as ListBoxItem).DataContext;
MessageBox.Show("to do: download stuff");
return;
}
catch (InvalidCastException)
{
}
try
{
dirWeWantSelected = (Directory)(sender as ListBoxItem).DataContext;
}
catch (InvalidCastException)
{
MessageBox.Show("this should never happen");
}
selectedDirectoryTreeItem.IsExpanded = true;
TreeViewItem want = null;
try
{
want = selectedDirectoryTreeItem.ItemContainerGenerator.ContainerFromItem(dirWeWantSelected) as TreeViewItem;
}
catch
{
MessageBox.Show("weird error");
}
if (want != null)
{
want.IsSelected = true;
}
else
{
selectedDirectoryTreeItem.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
}
}
void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (selectedDirectoryTreeItem.ItemContainerGenerator.Status
== System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
{
selectedDirectoryTreeItem.ItemContainerGenerator.StatusChanged
-= ItemContainerGenerator_StatusChanged;
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input,
new Action(DelayedAction));
}
}
void DelayedAction()
{
selectedDirectoryTreeItem.Items.MoveCurrentToFirst();
Directory curr;
do
{
curr = (Directory)selectedDirectoryTreeItem.Items.CurrentItem;
if (curr.id == dirWeWantSelected.id)
{
curr.Selected = true;
return;
}
selectedDirectoryTreeItem.Items.MoveCurrentToNext();
}
while (selectedDirectoryTreeItem.Items.CurrentItem != null);
}

Related

ComboBox, is it possible to have ItemsSource and SelectedValue bound to different sources?

I have two different objects that are pointing at each other. The first object represents a division in a company. That object has two collection: Employees, which is all the employees working in the division and Project, which is all the special projects that are in progress within that division. So the first object looks like this:
public class Division : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
ObservableCollection<Employee> _employees;
ObservableCollection<Project> _projects;
public Division()
{
Employees = new ObservableCollection<Employee>();
Projects = new ObservableCollection<Project>();
}
public ObservableCollection<Employee> Employees
{
get { return _employees; }
set
{
if (_employees != value)
{
_employees = value;
PropertyChanged(this, new PropertyChangedEventArgs("Employees"));
}
}
}
public ObservableCollection<Project> Projects
{
get { return _projects; }
set
{
if (_projects != value)
{
_projects = value;
PropertyChanged(this, new PropertyChangedEventArgs("Projects"));
}
}
}
public void AddNewProject()
{
this.Projects.Add(new Project(this));
}
}
Notice that when adding a new project to the division, I pass a reference to the division into that project, which looks like this:
public class Project : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
string _projectName;
DateTime _deadline = DateTime.Now;
Division _division;
ObservableCollection<Employee> _members;
public Project()
{
Members = new ObservableCollection<Employee>();
}
public Project(Division div)
{
Members = new ObservableCollection<Employee>();
Division = div;
}
public string ProjectName
{
get { return _projectName; }
set
{
if (_projectName != value)
{
_projectName = value;
PropertyChanged(this, new PropertyChangedEventArgs("ProjectName"));
}
}
}
public DateTime Deadline
{
get { return _deadline; }
set
{
if (_deadline != value)
{
_deadline = value;
PropertyChanged(this, new PropertyChangedEventArgs("Deadline"));
}
}
}
public Division Division
{
get { return _division; }
set
{
if (_division != value)
{
if (_division != null)
{
_division.Employees.CollectionChanged -= members_CollectionChanged;
}
_division = value;
if (_division != null)
{
_division.Employees.CollectionChanged += members_CollectionChanged;
}
PropertyChanged(this, new PropertyChangedEventArgs("Division"));
}
}
}
public ObservableCollection<Employee> Members
{
get { return _members; }
set
{
if (_members != value)
{
if (_members != null)
{
_members.CollectionChanged -= members_CollectionChanged;
}
_members = value;
if (_members != null)
{
_members.CollectionChanged += members_CollectionChanged;
}
PropertyChanged(this, new PropertyChangedEventArgs("Members"));
}
}
}
public ObservableCollection<Employee> AvailableEmployees
{
get
{
if (Division != null){
IEnumerable<Employee> availables =
from s in Division.Employees
where !Members.Contains(s)
select s;
return new ObservableCollection<Employee>(availables);
}
return new ObservableCollection<Employee>();
}
}
void members_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
PropertyChanged(this, new PropertyChangedEventArgs("AvailableEmployees"));
}
}
The reason I'm doing it like this is, that the project could have any type of team working on it, but only from within the division. So, when building a dashboard for the division, the manager could select any of the employees to that project but without putting in an employee that is already assigned to it. So, the AvailableEmployees property in the project object always keeps track of who is not already assigned to that project.
The problem I'm having is how to translate this into a UI. The experiment I've done so far looks like this:
<UserControl x:Class="Test.Views.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:local="clr-namespace:Test.Views"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<ListBox ItemsSource="{Binding Div.Projects}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="Transparent"
BorderThickness="0, 0, 0, 2"
BorderBrush="Black"
Margin="0, 0, 0, 5"
Padding="0, 0, 0, 5">
<StackPanel>
<TextBox Text="{Binding ProjectName}"/>
<ListBox ItemsSource="{Binding Members}">
<ListBox.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:TestView}, Path=DataContext.AvailableEmployees}"
DisplayMemberPath="FirstName"
Text="{Binding FirstName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Add Employee to Project"
Command="{Binding RelativeSource={RelativeSource AncestorType=local:TestView}, Path=DataContext.AddEmployeeToProject}"
CommandParameter="{Binding}"/>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Add New Project"
Command="{Binding AddNewProject}" />
</StackPanel>
The view model associated with this view is as follows:
public class TestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private Division _div;
public TestViewModel(Division div)
{
Div = div;
AddNewProject = new DelegateCommand(OnAddNewProject);
AddEmployeeToProject = new DelegateCommand<Project>(OnAddEmployeeToProject);
}
public DelegateCommand AddNewProject { get; set; }
public DelegateCommand<Project> AddEmployeeToProject { get; set; }
public Division Div
{
get { return _div; }
set
{
if (_div != value)
{
_div = value;
PropertyChanged(this, new PropertyChangedEventArgs("Div"));
}
}
}
private void OnAddNewProject()
{
Div.AddNewProject();
}
private void OnAddEmployeeToProject(Project proj)
{
var availables = proj.AvailableEmployees;
if (availables.Count > 0)
{
proj.Members.Add(availables[0]);
}
}
}
However, I cannot get the combobox for each employee in each project to work. It seems like the selected item/value is bound to the itemssource, and each time the combobox turns out blank. I've tried to do this also with SelectedValue and SelectedItem properties for the combobox, but none worked.
How do I get these two separated. Is there anything else I'm missing here?
OK. After so many experiments the best solution I came up with was to create my own user control that is composed of both a button and a combobox that imitate the behavior I was expecting of the combobox on it own.
First, I had a really stupid mistake in the model where both lists of members Project and Division contain the same instances of Employee, which makes the AvailableEmployees property buggy. What I really needed to do is to create a list of copies of employees in the Project instead of just references.
In any case, I created a new user control and called it DynamicSourceComboBox. The XAML of this control looks like this:
<Grid>
<Button x:Name="selected"
Content="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=SelectedValue}"
Click="selected_Click"/>
<ComboBox x:Name="selections"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=ItemsSource}"
DisplayMemberPath="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=DisplayMemberPath}"
Visibility="Collapsed"
SelectionChanged="selections_SelectionChanged"
MouseLeave="selections_MouseLeave"/>
</Grid>
I have here a few bindings from the button and the combobox to properties in my user control. These are actually dependency properties. The code-behind of my user control looks like this:
public partial class DynamicSourceComboBox : UserControl
{
public DynamicSourceComboBox()
{
InitializeComponent();
}
public object SelectedValue
{
get { return (object)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(object), typeof(DynamicSourceComboBox), new PropertyMetadata(null));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
ComboBox.ItemsSourceProperty.AddOwner(typeof(DynamicSourceComboBox));
public string DisplayMemberPath
{
get { return (string)GetValue(DisplayMemberPathProperty); }
set { SetValue(DisplayMemberPathProperty, value); }
}
public static readonly DependencyProperty DisplayMemberPathProperty =
ComboBox.DisplayMemberPathProperty.AddOwner(typeof(DynamicSourceComboBox));
private void selected_Click(object sender, RoutedEventArgs e)
{
selected.Visibility = Visibility.Hidden;
selections.Visibility = Visibility.Visible;
selections.IsDropDownOpen = true;
}
private void selections_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
selections.Visibility = Visibility.Collapsed;
selected.Visibility = Visibility.Visible;
selections.IsDropDownOpen = false;
if (e.AddedItems.Count == 1)
{
var item = e.AddedItems[0];
Type itemType = item.GetType();
var itemTypeProps = itemType.GetProperties();
var realValue = (from prop in itemTypeProps
where prop.Name == DisplayMemberPath
select prop.GetValue(selections.SelectedValue)).First();
SelectedValue = realValue;
}
}
private void selections_MouseLeave(object sender, MouseEventArgs e)
{
selections.Visibility = Visibility.Collapsed;
selected.Visibility = Visibility.Visible;
selections.IsDropDownOpen = false;
}
}
These dependency properties imitate the properties with similar names in ComboBox but they are hooked up to the internal combobox and the button in a way that makes them behave together as a single complex combobox.
The Click event in the button hides it and present the combobox to make the effect of just a box that is opening. Then I have a SelectionChanged event in the combobox firing to update all the needed information and a MouseLeave event just in case the user doesn't make any real selection change.
When I need to use the new user control, I set it up like this:
<local:DynamicSourceComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorLevel=1, AncestorType=ListBox}, Path=DataContext.AvailableEmployees}"
DisplayMemberPath="FirstName"
SelectedValue="{Binding FirstName, Mode=TwoWay}"/>
Of course, for all of it to work, I have to make a lot of hookups with PropertyChanged events in the models, so the Projects instance will know to raise a PropertyChanged event for AvailableEmployees any time a change is made, but this is not really the concern of this user control itself.
This is a pretty clunky solution, with a lot of extra code that is a bit hard to follow, but it's really the best (actually only) solution I could have come up with to the problem I had.

How to delete selected rows (using checkbox) in wpf datagrid

My WPF DataGrid is:
<dg:DataGrid Name="datagrid1" Grid.RowSpan="1" VerticalAlignment="Stretch" Grid.ColumnSpan="2">
<dg:DataGrid.Columns >
<dg:DataGridTemplateColumn>
<dg:DataGridTemplateColumn.Header>
<CheckBox Content=" Slect All" x:Name="headerCheckBox" />
</dg:DataGridTemplateColumn.Header>
<dg:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="chkSelectAll" Margin="45 2 0 0"
IsChecked="{Binding IsChecked, ElementName=headerCheckBox,
Mode=OneWay}" />
</DataTemplate>
</dg:DataGridTemplateColumn.CellTemplate>
</dg:DataGridTemplateColumn>
</dg:DataGrid.Columns>
</dg:DataGrid>
Also Dynamicaly I am populating the data to the datgrid.In xaml.cs file I written the below given code for deleting the selected row from the data grid but it throwing the error at line
DataGridRow item =(DataGridRow) datagrid1.ItemContainerGenerator.ContainerFromItem(datagrid1.Items[j]);
So Please have a look in to the below given code which I written for doing the same.
private void Button_Click_1(object sender, RoutedEventArgs e)
{
for (int j = 0; j < datagrid1.Items.Count; j++)
{
DataGridRow item =(DataGridRow) datagrid1.ItemContainerGenerator.ContainerFromItem(datagrid1.Items[j]);
CheckBox ckb = (CheckBox)GetVisualChild<CheckBox>(item);
if (ckb.IsChecked.Value)
{
DataRowView drv = (DataRowView)datagrid1.Items[j];
//delete the row- updating to the database
}
}
}
static T GetVisualChild<T>(Visual 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;
}
Please let me know if am wrong.
Here is how I would do this. Implement an ObservableCollection of your class that inherits INotifyPropertyChanged. INotifyPropertyChanged will be used in case we want to update items in the collection.
First the xaml for the GridView
<DataGrid x:Name="gvMain" AutoGenerateColumns="True" HorizontalAlignment="Left"
VerticalAlignment="Top" Height="300" Width="300"></DataGrid>
Our class of items
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string firstName { get; set; }
private string lastName { get; set; }
public string FirstName
{
get
{
return firstName;
}
set
{
firstName = value;
PropertyChangedEvent("FirstName");
}
}
public string LastName
{
get
{
return lastName;
}
set
{
lastName = value;
PropertyChangedEvent("LastName");
}
}
private void PropertyChangedEvent(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Next the WPF window
public ObservableCollection<MyClass> gridData { get; set; }
public MainWindow()
{
InitializeComponent();
gridData = new ObservableCollection<MyClass>();
gvMain.ItemsSource = gridData;
}
Test to add, change, delete items in the collection
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
gridData.Add(new MyClass() { FirstName = "John", LastName = "Smith" });
}
private void btnChange_Click(object sender, RoutedEventArgs e)
{
gridData[0].FirstName = "Meow Mix";
}
private void btnDelete_Click(object sender, RoutedEventArgs e)
{
//using List to use .ForEach less code to write and looks cleaner to me
List<MyClass> remove = gridData.Where(x => x.LastName.Equals("Smith")).ToList();
remove.ForEach(x => gridData.Remove(x));
}
Any changes you want to make will be done with gridData.

How to Draw line/s between Two DataGridView Controls

i have two grid on Window form , i need to show mapping by lines between cell/s from one grid to other grid when user click on first grid cell and also this functionality works with scroll bar means line position will change according to the cell position when user moves vertical scroll bar.
please use below link to see image for more clarification.
http://s8.postimg.org/49s7i2lvp/Mapping.png
Any help is appreciated and Thanks in advance
Best Regards
Shailesh
Ok. Im posting this as an answer because the OP asked for it.
This is my WPF take on that:
<Window x:Class="MiscSamples.DataGridConnectors"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataGridConnectors" Height="300" Width="300">
<Grid x:Name="Root">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ItemsControl ItemsSource="{Binding VisibleConnectors}" Grid.ColumnSpan="3">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding StartPoint.X}"
Y1="{Binding StartPoint.Y}"
X2="{Binding EndPoint.X}"
Y2="{Binding EndPoint.Y}"
Stroke="Black"
StrokeThickness="2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<DataGrid ItemsSource="{Binding Items1}" x:Name="DG1" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=.}"/>
</DataGrid.Columns>
</DataGrid>
<DataGrid ItemsSource="{Binding Items2}" x:Name="DG2" AutoGenerateColumns="False" Grid.Column="2">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=.}"/>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Column="1">
<Button Content="Sequential" Click="Sequential_Click"/>
<Button Content="Random" Click="Random_Click"/>
</StackPanel>
</Grid>
</Window>
Code Behind:
public partial class DataGridConnectors : Window
{
public List<string> Items1 { get; set; }
public List<string> Items2 { get; set; }
public List<DataItemConnector> Connectors { get; set; }
private ObservableCollection<DataItemConnector> _visibleConnectors;
public ObservableCollection<DataItemConnector> VisibleConnectors
{
get { return _visibleConnectors ?? (_visibleConnectors = new ObservableCollection<DataItemConnector>()); }
}
public DataGridConnectors()
{
Connectors = new List<DataItemConnector>();
InitializeComponent();
Loaded += OnLoaded;
Items1 = Enumerable.Range(0, 1000).Select(x => "Item1 - " + x.ToString()).ToList();
Items2 = Enumerable.Range(0, 1000).Select(x => "Item2 - " + x.ToString()).ToList();
DataContext = this;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
var scrollviewer1 = FindDescendent<ScrollViewer>(DG1).FirstOrDefault();
var scrollviewer2 = FindDescendent<ScrollViewer>(DG2).FirstOrDefault();
if (scrollviewer1 != null)
scrollviewer1.ScrollChanged += scrollviewer_ScrollChanged;
if (scrollviewer2 != null)
scrollviewer2.ScrollChanged += scrollviewer_ScrollChanged;
}
private void scrollviewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var visiblerows1 = GetVisibleContainers(Items1, DG1.ItemContainerGenerator);
var visiblerows2 = GetVisibleContainers(Items2, DG2.ItemContainerGenerator);
var visibleitems1 = visiblerows1.Select(x => x.DataContext);
var visibleitems2 = visiblerows2.Select(x => x.DataContext);
var visibleconnectors = Connectors.Where(x => visibleitems1.Contains(x.Start) &&
visibleitems2.Contains(x.End));
VisibleConnectors.Where(x => !visibleconnectors.Contains(x))
.ToList()
.ForEach(x => VisibleConnectors.Remove(x));
visibleconnectors.Where(x => !VisibleConnectors.Contains(x))
.ToList()
.ForEach(x => VisibleConnectors.Add(x));
foreach(var connector in VisibleConnectors)
{
var startrow = visiblerows1.FirstOrDefault(x => x.DataContext == connector.Start);
var endrow = visiblerows2.FirstOrDefault(x => x.DataContext == connector.End);
if (startrow != null)
connector.StartPoint = Point.Add(startrow.TransformToAncestor(Root).Transform(new Point(0, 0)),
new Vector(startrow.ActualWidth + 5, (startrow.ActualHeight / 2)*-1));
if (endrow != null)
connector.EndPoint = Point.Add(endrow.TransformToAncestor(Root).Transform(new Point(0, 0)),
new Vector(-5,(endrow.ActualHeight / 2 ) * -1));
}
}
private static List<FrameworkElement> GetVisibleContainers(IEnumerable<object> source, ItemContainerGenerator generator)
{
return source.Select(generator.ContainerFromItem).Where(x => x != null).OfType<FrameworkElement>().ToList();
}
public static List<T> FindDescendent<T>(DependencyObject element) where T : DependencyObject
{
var f = new List<T>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
var child = VisualTreeHelper.GetChild(element, i);
if (child is T)
f.Add((T)child);
f.AddRange(FindDescendent<T>(child));
}
return f;
}
private void Sequential_Click(object sender, RoutedEventArgs e)
{
Connectors.Clear();
Enumerable.Range(0, 1000).Select(x => new DataItemConnector() { Start = Items1[x], End = Items2[x] })
.ToList()
.ForEach(x => Connectors.Add(x));
scrollviewer_ScrollChanged(null, null);
}
private void Random_Click(object sender, RoutedEventArgs e)
{
Connectors.Clear();
var random = new Random();
Enumerable.Range(500, random.Next(600, 1000))
.Select(x => new DataItemConnector()
{
Start = Items1[random.Next(0, 999)],
End = Items2[random.Next(0, 999)]
})
.ToList()
.ForEach(Connectors.Add);
scrollviewer_ScrollChanged(null, null);
}
}
Connector:
public class DataItemConnector: PropertyChangedBase
{
public object Start { get; set; }
public object End { get; set; }
private Point _startPoint;
public Point StartPoint
{
get { return _startPoint; }
set
{
_startPoint = value;
OnPropertyChanged("StartPoint");
}
}
private Point _endPoint;
public Point EndPoint
{
get { return _endPoint; }
set
{
_endPoint = value;
OnPropertyChanged("EndPoint");
}
}
}
Base class to support two way binding:
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
Application.Current.Dispatcher.BeginInvoke((Action) (() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}
Result:
Resolution independent. Try resizing the window and see it for yourself.
The code is really really simple. Most of the code behind is actually boilerplate to support the example (generate random values, etc).
No "owner draw", No P/Invoke. Just simple, simple properties and INotifyPropertyChanged.
WPF rules. Just copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.
Something like :
foreach (DataGridViewRow row in dataGrid.Rows)
{
var cellValue = row.Cells["field"].Value;
if (cellValue != null && cellValue.ToString() == "something")
{
dataGrid.Rows[row.Index].Selected = true;
try
{
dataGrid.FirstDisplayedScrollingRowIndex = row.Index - 4;
}
catch (Exception execc)
{
dataGrid.FirstDisplayedScrollingRowIndex = row.Index;
}
}
}
this?
then use the same for the other grid i suppose

How to write check if a TextBlock consists a specific string in windows phone 7 XAML?

I've have a textblock and a textbox, I need to check and whether the textblock contains the textbox value, if yes then that specific value should be highlighted in some color. . .
For example,
TextBlock.Text="Just a Test"
if we type "te" in TextBox, then the value in the textblock should be highlight as "Just a *Te*st"
in xaml.
if anybody know means please say!
Thanks in Advance!
Hi i found the solution here's the underlying code that has been designed in MVVM pattern :
EDIT : ** Pages -> MainPage.xaml.cs and ListViewModal has been changed according to ur need .
** was having some issue binding data from view modal so gave a quick response by doing it in code behind
MainPage.xaml
<phone:PhoneApplicationPage
x:Class="FirstAppInCSharp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" Width="Auto" Height="Auto"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True" d:DesignHeight="768" d:DesignWidth="480">
<Grid Name='LayoutRoot'>
<ListBox Height='500' Width='500' Background='Red'
Name="ContactList"
Margin="14,85,14,28" Loaded='ContactList_Loaded'
Foreground="Black"
ItemsSource='{Binding ListOftext}'
>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel
Background='AliceBlue' Height='100' Width='500'
Orientation="Horizontal">
<TextBlock
FontSize='30'
Height='70'
Foreground='Black'
Text='{Binding DisplayName}' Width='300'
/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox
Height='72'
HorizontalAlignment='Left'
Margin='8,27,0,0'
Name='textBox1'
TextChanged='textBox1_TextChanged'
VerticalAlignment='Top'
Width='460' />
</Grid>
Now the MainPage.xaml.cs
public partial class MainPage : PhoneApplicationPage
{
TextBlock textBlock1 = null;
List<TextBlock> listText = null;
// Constructor
public MainPage()
{
InitializeComponent();
Contacts contact = new Contacts();
contact.SearchAsync("", FilterKind.DisplayName, null);
contact.SearchCompleted += new EventHandler<ContactsSearchEventArgs>(contact_SearchCompleted);
}
void contact_SearchCompleted(object sender, ContactsSearchEventArgs e)
{
ContactList.DataContext = new ListViewModal(e.Results);
}
private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
SearchVisualTree(ContactList);
}
private void SearchVisualTree(DependencyObject targetElement)
{
var count = VisualTreeHelper.GetChildrenCount(targetElement);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(targetElement, i);
if (child is TextBlock)
{
textBlock1 = (TextBlock)child;
HighlightText();
break;
}
else
{
SearchVisualTree(child);
}
}
}
private void HighlightText()
{
if (textBlock1 != null)
{
string text = textBlock1.Text;
textBlock1.Text = text;
textBlock1.Inlines.Clear();
int index = text.IndexOf(textBox1.Text);
int lenth = textBox1.Text.Length;
if (!(index < 0))
{
Run run = new Run() { Text = text.Substring(index, lenth), FontWeight = FontWeights.ExtraBold };
run.Foreground = new SolidColorBrush(Colors.Orange);
textBlock1.Inlines.Add(new Run() { Text = text.Substring(0, index), FontWeight = FontWeights.Normal });
textBlock1.Inlines.Add(run);
textBlock1.Inlines.Add(new Run() { Text = text.Substring(index + lenth), FontWeight = FontWeights.Normal });
textBlock1.FontSize = 30;
textBlock1.Foreground = new SolidColorBrush(Colors.Black);
}
else
{
textBlock1.Text = "No Match";
}
}
}
private void ContactList_Loaded(object sender, RoutedEventArgs e)
{
}
}
now the view modal for the list (I have added data manually)
public class ListViewModal : INotifyPropertyChanged
{
public List<CheckList> ListOftext { get; set; }
public ListViewModal(IEnumerable<Contact> iEnumerable)
{
ListOftext = new List<CheckList>();
foreach (var list in iEnumerable)
{
ListOftext.Add(new CheckList(){DisplayName = list.DisplayName});
}
RaisePropertyChanged("ListOftext");
}
/// <summary>
/// Property changed method
/// Executes when a property changes its value
/// </summary>
/// <param name="propertyName"></param>
public void RaisePropertyChanged(string propertyName)
{
// this is the property changed method
//to detect property change
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private IEnumerable<Contact> iEnumerable;
}
now the data binding class (MOdel class)
public class CheckList
{
public string DisplayName { get; set; }
}
thanks :) you can ask further questions if u want to or if you have any doubts .
Heres a sample code to be put in the text box TextChange event
private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
string text = "this is a TextBlock";
textBlock1.Text = text;
textBlock1.Inlines.Clear();
int index = text.IndexOf(textBox1.Text);
int lenth = textBox1.Text.Length;
if (!(index < 0))
{
Run run = new Run() { Text = text.Substring(index, lenth), FontWeight = FontWeights.ExtraBold };
run.Foreground = new SolidColorBrush(Colors.Orange);
textBlock1.Inlines.Add(new Run() { Text = text.Substring(0, index), FontWeight = FontWeights.Normal });
textBlock1.Inlines.Add(run);
textBlock1.Inlines.Add(new Run() { Text = text.Substring(index + lenth), FontWeight = FontWeights.Normal });
textBlock1.FontSize = 30;
textBlock1.Foreground = new SolidColorBrush(Colors.White);
}
else
{
textBlock1.Text = "No Match";
}
}
this will highlight the text and return no match is no match is found.
note : this code snippet is case sensitive.

WPF TreeView leaking the selected item

I currently have a strange memory leak with WPF TreeView. When I select an item in the TreeView, the corresponding bound ViewModel is strongely hold in the TreeView EffectiveValueEntry[] collection. The issue is that it is not released when the ViewModel is removed from it's parent collection.
Here is a simple code to reproduce the issue :
MainWindow.xaml
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls.Primitives;
namespace TreeViewMemoryLeak
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public ObservableCollection<Entry> Entries
{
get
{
if (entries == null)
{
entries = new ObservableCollection<Entry>() { new Entry() { DisplayName = "First Entry" } };
}
return entries;
}
}
private void Button_Click(object sender, RoutedEventArgs e) { entries.Clear(); }
private ObservableCollection<Entry> entries;
}
public class Entry : DependencyObject
{
public string DisplayName { get; set; }
}
}
MainWindow.xaml.cs
<Window x:Class="TreeViewMemoryLeak.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeViewMemoryLeak"
Title="MainWindow" Height="350" Width="250">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Entry}">
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Content="delete item" Click="Button_Click" Grid.Row="0" Margin="10"/>
<TreeView x:Name="treeView" ItemsSource="{Binding Entries}" Grid.Row="1" Margin="10" BorderBrush="Black" BorderThickness="1" />
</StackPanel>
</Window>
To reproduce the issue
Select the item, then click the button to clear the ObservableCollection. Now check the EffectiveValueEntry[] on the TreeView control : the ViewModel is still there and is not flagged for garbage collection.
Well I finally came up with a rather violent solution. I remove the reference from the EffectiveValues collection myself when deleting the last object in the TreeView. It may be overkill but at least, it works.
public class MyTreeView : TreeView
{
protected override void OnSelectedItemChanged(RoutedPropertyChangedEventArgs<object> e)
{
base.OnSelectedItemChanged(e);
if (Items.Count == 0)
{
var lastObjectDeleted = e.OldValue;
if (lastObjectDeleted != null)
{
var effectiveValues = EffectiveValuesGetMethod.Invoke(this, null) as Array;
if (effectiveValues == null)
throw new InvalidOperationException();
bool foundEntry = false;
int index = 0;
foreach (var effectiveValueEntry in effectiveValues)
{
var value = EffectiveValueEntryValueGetMethod.Invoke(effectiveValueEntry, null);
if (value == lastObjectDeleted)
{
foundEntry = true;
break;
}
index++;
}
if (foundEntry)
{
effectiveValues.SetValue(null, index);
}
}
}
}
protected MethodInfo EffectiveValueEntryValueGetMethod
{
get
{
if (effectiveValueEntryValueGetMethod == null)
{
var effectiveValueEntryType = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).Where(t => t.Name == "EffectiveValueEntry").FirstOrDefault();
if (effectiveValueEntryType == null)
throw new InvalidOperationException();
var effectiveValueEntryValuePropertyInfo = effectiveValueEntryType.GetProperty("Value", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Instance);
if (effectiveValueEntryValuePropertyInfo == null)
throw new InvalidOperationException();
effectiveValueEntryValueGetMethod = effectiveValueEntryValuePropertyInfo.GetGetMethod(nonPublic: true);
if (effectiveValueEntryValueGetMethod == null)
throw new InvalidOperationException();
}
return effectiveValueEntryValueGetMethod;
}
}
protected MethodInfo EffectiveValuesGetMethod
{
get
{
if (effectiveValuesGetMethod == null)
{
var dependencyObjectType = typeof(DependencyObject);
var effectiveValuesPropertyInfo = dependencyObjectType.GetProperty("EffectiveValues", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Instance);
if (effectiveValuesPropertyInfo == null)
throw new InvalidOperationException();
effectiveValuesGetMethod = effectiveValuesPropertyInfo.GetGetMethod(nonPublic: true);
if (effectiveValuesGetMethod == null)
throw new InvalidOperationException();
}
return effectiveValuesGetMethod;
}
}
#region Private fields
private MethodInfo effectiveValueEntryValueGetMethod;
private MethodInfo effectiveValuesGetMethod;
#endregion
}
It is because you binded your treeview with OneTime mode, so your collection was 'snapshotted'. As stated:
UPDATED:
EffectiveValueEntry is about how DependencyObjects store the values of their DependencyProperties. This collection will hold object as long as treeView has selected item. As soon as you select something else collection will be updated.
I had the same issue and solved it using one of the solutions on this link (posted by Tom Goff). Do the following:
ClearSelection(this.treeView);
this.treeView.SelectedValuePath = ".";
this.treeView.ClearValue(TreeView.SelectedValuePathProperty);
this.treeView.ItemsSource = null;
...
public static void ClearSelection(TreeView treeView)
{
if (treeView != null)
ClearSelection(treeView.Items, treeView.ItemContainerGenerator);
}
private static void ClearSelection(ItemCollection collection, ItemContainerGenerator generator)
{
if ((collection != null) && (generator != null))
{
for (int i = 0; i < collection.Count; i++)
{
TreeViewItem treeViewItem = generator.ContainerFromIndex(i) as TreeViewItem;
if (treeViewItem != null)
{
ClearSelection(treeViewItem.Items, treeViewItem.ItemContainerGenerator);
treeViewItem.IsSelected = false;
}
}
}
}

Categories