I have a hub control with two HubSections. When selected HubSection changes, I want to change the contents of the AppBar with section specific buttons.
Listening SectionsInViewChanged event is the general solution recommended to implement this behavior but this event is not fired when there are only two HubSections.
Is there another event that can be used to determine the current HubSection?
Thanks.
#Depechie has pointed you in the right direction.. You can use the SelectionHub control I created and add an event to it that fires when the selected index changes
public event EventHandler SelectionChanged;
private static void OnSelectedIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var hub = d as SelectionHub;
if (hub == null) return;
// do not try to set the section when the user is swiping
if (hub._settingIndex) return;
// No sections?!?
if (hub.Sections.Count == 0) return;
hub.OnSelectionChanged();
}
private void OnSelectionChanged()
{
var section = Sections[SelectedIndex];
ScrollToSection(section);
var handler = SelectionChanged;
if(handler != null)
{
handler(this, EventArgs.Empty);
}
}
You can extend this by adding selection changed event args to it. With this you can then subscribe to the event in your code behind and change the app bar buttons based on the index.
After reading Shawn's article suggested by #Depechie. I tried to implement the same solution in my app in order to update contents of the AppBar with section specific buttons.
Despite my efforts I was unable to make it work so I modified some parts of the solution. I used the behavior solution and changed only the ScrollerOnViewChangedfunction as follows. This might not be the best way or may cause unexpected results in different scenarios but in my case it worked without a problem.
private void ScrollerOnViewChanged(object sender, ScrollViewerViewChangedEventArgs scrollViewerViewChangedEventArgs)
{
_settingIndex = true;
ScrollViewer scrollViewer = sender as ScrollViewer;
if (scrollViewer.HorizontalOffset > (scrollViewer.ViewportWidth / 2))
SelectedIndex = 1;
else
SelectedIndex = 0;
_settingIndex = false;
}
After that I added a property to my viewmodel in order to store selected index.
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
SetProperty(ref this._selectedIndex, value);
}
}
I used the behavior in the XAML in order to update SelectedIndex in my ViewModel.
<Hub>
<i:Interaction.Behaviors>
<behaviors:HubSelectionBehavior SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" />
</i:Interaction.Behaviors>
<HubSection>...</HubSection>
<HubSection>...</HubSection>
</Hub>
The last thing to do was to set the visibility of AppBarButtons using this property. SectionIndexToVisibilityConverter compares SelectedIndex to ConverterParameter and returns Visibility.Visible if they are equal.
<CommandBar>
<AppBarButton Label="Open" Icon="World" Command="{Binding OpenInBrowserCommand}" Visibility="{Binding SelectedIndex, Converter={StaticResource SectionIndexToVisibilityConverter}, ConverterParameter=0}"/>
<AppBarButton Label="Previous" Icon="Back" Command="{Binding PreviousAnswerCommand}" Visibility="{Binding SelectedIndex, Converter={StaticResource SectionIndexToVisibilityConverter}, ConverterParameter=1}"/>
<AppBarButton Label="Next" Icon="Forward" Command="{Binding NextAnswerCommand}" Visibility="{Binding SelectedIndex, Converter={StaticResource SectionIndexToVisibilityConverter}, ConverterParameter=1}"/>
</CommandBar>
Thanks #Depechie for suggesting the article and #Shawn for writing the article : )
Related
I have a row in a grid with 5 textboxes, 2 of which are enabled by checkboxes. I am trying to dynamically add additional rows to the grid when a button is clicked. The eventhandler I added will only enable the textbox in the first row, but not in the current row (2nd). There is another eventhandler which handles the box in the first row, this is a new one. (BTW I only have part of the second row coded). Not sure if I should try making a template for the checkbox, and then use binding to the textbox? And if so, the instructions I've read on connecting the binding are vague and confusing. Or can I do the binding directly? Or ?
public partial class Window2 : Window
{
int currentColumn = 0;
int currentRow = 1;
int timesCalled = 1;
public Window2()
{
InitializeComponent();
}
private void AddLevelButton_Click(object sender, RoutedEventArgs e)
{
string level = this.Level.Content.ToString(); //label for the row
string[] splitLevel = level.Split(' ');
int levelNum = int.Parse(splitLevel[1]);
levelNum = timesCalled + 1;
int nextRow = currentRow + 1;
int nextColumn = currentColumn + 1;
Label levelLabel = new Label();
levelLabel.Content = "Level " + levelNum.ToString();
Grid.SetRow(levelLabel, nextRow);
Grid.SetColumn(levelLabel, currentColumn);
FlowGrid.Children.Add(levelLabel);
currentColumn++;
CheckBox antesBox = new CheckBox(); //the checkbox to enable the
antesBox.Name = "AntesBox"; //textbox which follows
antesBox.VerticalAlignment = VerticalAlignment.Bottom;
antesBox.HorizontalAlignment = HorizontalAlignment.Right;
antesBox.FontSize = 16;
antesBox.Width = 20;
antesBox.Height = 20;
antesBox.Checked += AntesBox_Checked1; //eventhandler
Grid.SetRow(antesBox, nextRow);
Grid.SetColumn(antesBox, currentColumn);
FlowGrid.Children.Add(antesBox);
nextColumn = ++currentColumn;
TextBox enterAntes = new TextBox(); //the textbox to be enabled
enterAntes.Name = "EnterAntes";
enterAntes.Margin = new Thickness(5, 0, 5, 0);
enterAntes.FontSize = 16;
enterAntes.FontFamily = new FontFamily("Verdana");
enterAntes.IsEnabled = false;
enterAntes.KeyDown += EnterAntes_KeyDown1; //tested; this works
Grid.SetRow(EnterAntes, nextRow);
Grid.SetColumn(EnterAntes, nextColumn);
FlowGrid.Children.Add(EnterAntes);
nextColumn = ++currentColumn;
}
private void enterAntes_KeyDown1(object sender, KeyEventArgs e)
{
int key = (int)e.Key;
e.Handled = !(key >= 34 && key <= 43 ||
key >= 74 && key <= 83 || key == 2);
}
private void AntesBox_Checked1(object sender, RoutedEventArgs e)
{
EnterAntes.IsEnabled = true;
}
You need to add following codes to enable text boxes.
Following is the xaml view of the datagrid.
<DataGrid x:Name="gvTest" AutoGenerateColumns="False" ItemsSource="{Binding}" HorizontalAlignment="Left" Margin="86,204,0,0" VerticalAlignment="Top" Height="132" Width="436">
<DataGrid.Columns>
<DataGridTemplateColumn Header="TextBox 01">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txt01" Width="50" Text="{Binding TxtBox01}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="TextBox 02">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbox02" Width="50" Text="{Binding TxtBox02}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="TextBox 03">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbox03" Width="50" Text="{Binding TxtBox03}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="TextBox 04">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbox04" Width="50" IsEnabled="False" Text="{Binding TxtBox04}" Loaded="txtbox04_Loaded"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="TextBox 05">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbox05" Text="{Binding TxtBox05}" Loaded="txtbox05_Loaded"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Enable" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="chk01" Checked="chk01_Checked" IsChecked="{Binding IsActive}"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
add the following codes to declare instance of required textboxes and declare observable collection.
TextBox txt04;
TextBox txt05;
ObservableCollection<TestItem> TestItemList = new ObservableCollection<TestItem>();
add the following codes to the loaded event of the required textboxes.
private void txtbox04_Loaded(object sender, RoutedEventArgs e)
{
txt04 = (sender as TextBox);
//txt04.IsEnabled = false;
}
private void txtbox05_Loaded(object sender, RoutedEventArgs e)
{
txt05 = (sender as TextBox);
}
Now, create a model class with following code segment in order to bind values to the datagrid.
public class TestItem
{
public string TxtBox01 { get; set; }
public string TxtBox02 { get; set; }
public string TxtBox03 { get; set; }
public string TxtBox04 { get; set; }
public string TxtBox05 { get; set; }
public bool IsActive { get; set; }
public TestItem()
{
IsActive = false;
}
}
I have used a button to add new rows to the datagrid. add the following codes to the button click to add rows.
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
TestItemList.Add(new TestItem());
gvTest.ItemsSource = TestItemList;
}
Finally, add the following codes to the checkbox checked event
CheckBox c = (sender as CheckBox);
if (c.IsChecked==true)
{
txt04.IsEnabled = true;
txt05.IsEnabled = true;
}
Hope this helps you to fulfill your requirement.
At the risk of perpetuating the wrong approach, it seems to me that the most direct way to address your specific need here is to fix your event handler so that it is always specific to the text box that corresponds to the checkbox in question. This is most easily done by moving the event handler subscription to below the declaration of the local variable enterAntes, and then use that variable in the event handler (i.e. so that it's capture by the anonymous method used as the event handler). For example:
TextBox enterAntes = new TextBox(); //the textbox to be enabled
antesBox.Checked += (sender, e) => enterAntes.IsEnabled = true;
Now, that said, I whole-heartedly agree with commenter Mark Feldman who suggests that the code you've written is not the right way to accomplish your goal in WPF.
I'm not sure I agree with the characterization "harder". That's such a loaded and subjective term, depending in no small part in what you find easy or hard. Being new to WPF, you almost certainly find the concepts of data binding and declarative XAML-based coding "hard", and direct, procedural code such as in your example "easy" (or at least "easier" :) ).
But he's absolutely right that in the long run, you will be better served by doing things "the WPF way". You may or may not wind up with much less code, but the WPF API is designed to be leveraged as much as possible from the XAML, and use code-behind minimally (and certainly not for the purpose to build the UI).
So what's all that mean for your code? Well, I ramble and it would be beyond the scope of a good, concise Stack Overflow answer for me to try to rewrite your entire code from scratch to suit the WPF paradigm. But I will offer some suggestions as to how I'd handle this.
First, forget the UI objects themselves for a moment. Write classes that describe the key characteristics of the UI as you want it to be, without being the UI itself. In this example, this could mean that you should have a list of rows. There should also be a class that defines what a single row looks like, e.g. with a bool property (to reflect the checkbox state) and a string property (to reflect the text box value). This is your "model"; i.e. each class is an individual model class, and at the same time you could consider the entire collection of classes as the model for your UI.
Now, go back to your UI and define it in XAML, not in code. There are several different ways to represent a list in the UI. Classes like ListBox, ListView, DataGrid, or even ItemsControl (the base class for many of the list-oriented controls). Bind the source of your list control to the model list you created in the previous step.
Define a DataTemplate (again, in XAML) for the type of class that is contained in the list. This will declare the UI for a single row in your list. Your template might look something like this:
<!-- Make sure you defined the "local" XML namespace for your project using the
xmlns declaration -->
<DataTemplate DataType="{x:Type local:MyRowModel}">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Text}" IsEnabled={Binding IsEnabled}"/>
<Checkbox Checked="{Binding IsEnabled}"/>
</StackPanel>
</DataTemplate>
All of the XAML inside the DataTemplate element tells WPF what you want a single row to look like, within the control that is presenting your row model. That control will set the DataContext for the list item defined by the template, such that the {Binding...} declarations can reference your row model's properties directly by name.
That row model in turn might look something like this:
class MyRowModel : INotifyPropertyChanged
{
private string _text;
private bool _isEnabled;
public string Text
{
get { return _text; }
set
{
if (_text != value)
{
_text = value;
OnPropertyChanged();
}
}
}
public bool IsEnabled
{
get { return _isEnabled; }
set
{
if (_isEnabled != value)
{
_isEnabled = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
When your button to add a new item is clicked, don't mess with the UI directly. Instead, add a new element to your list of rows. Let WPF do the work of updating the UI to match.
NOTES:
The above uses StackPanel for the data template for convenience. If you want things lined up in columns, you'll probably want to use a Grid and declare its columns using SharedSizeGroup.
Or better yet, maybe you can use DataGrid which, assuming its default presentation of the values is acceptable to you, offers simple and automatic handling of exactly this type of layout.
The above is not meant to be anything close to a complete explanation of how to use data templating. It's just supposed to get you pointed in the right direction. Templating is one of WPF's more powerful features, but with that it also has the potential to be fairly complex.
For all of this to work, your types need to provide notification when they change. In the case of the row model, you can see it implements INotifyPropertyChanged. There is also an interface INotifyCollectionChanged; usually you don't have to implement this yourself, as WPF has the type ObservableCollection<T> which you can use just like List<T>, to store lists of data but with a way for notifications of changes to be reported to WPF.
I know this is a lot to take it all at once. Unfortunately, it's not feasible to try to explain all in a single answer all the things you need to learn to get this right. Frankly, even the above is pushing the limits as to what's within the scope of a Stack Overflow answer. But I hope that I've hit just the right highlights to get you looking at the right parts of the WPF documentation, and to understand the underlying philosophy of the WPF API.
I have a WPF Datagrid that is displaying a structured log from a back-end system. The logs can be gigantic, so I only fetch a few hundred entries at time. I want to trigger retrieving more entries when the scrollbar thumb hits the 'bottom' of the scroll area.
I found this code-behind solution for sensing the end of the scroll, but I'm using Caliburn Micro. So I tried hooking up the ScrollChanged event, so I could process it in the view model (not my favorite solution, but I appear to be running out of options). The "obvious" implicit caliburn binding of
cal:Message.Attach="[Event ScrollViewer.ScrollChanged] = [Action DoScrollAction($eventArgs)]"
doesn't work, and neither did the explicit
<i:Interaction.Triggers>
<i:EventTrigger EventName="ScrollViewer.ScrollChanged">
<cal:ActionMessage MethodName="DoScrollAction">
<cal:Parameter Value="$eventargs" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
approach. Is there something baked into Caliburn Micro that addresses these attached events? Is there a better event to use for sensing the end of the scroll area in a Datagrid that doesn't have me processing ScrollChanged events in a view model?
So I discovered Joymon's solution for addressing the attached events after posting my question, and used his RoutedEventTrigger class, combined with the code sensing the end-of-scroll condition in my view model.
For the record, here are the pieces of the solution:
xmlns:wpfCommon="clr-namespace:WPFCommon;assembly=WPFCommon"
...
<DataGrid x:Name="SCPLog">
<i:Interaction.Triggers>
<wpfCommon:RoutedEventTrigger RoutedEvent="ScrollViewer.ScrollChanged">
<cal:ActionMessage MethodName="DoScroll">
<cal:Parameter Value="$eventargs" />
</cal:ActionMessage>
</wpfCommon:RoutedEventTrigger>
</i:Interaction.Triggers>
</DataGrid>
And a variation on Joymon's RoutedEventTrigger, which I placed in my own WPFCommon library:
public class RoutedEventTrigger : EventTriggerBase<DependencyObject>
{
public RoutedEvent RoutedEvent { get; set; }
protected override void OnAttached()
{
var behavior = base.AssociatedObject as Behavior;
var associatedElement = base.AssociatedObject as FrameworkElement;
if (behavior != null)
associatedElement = ((IAttachedObject)behavior).AssociatedObject as FrameworkElement;
if (associatedElement == null)
throw new ArgumentException("Routed Event trigger can only be associated to framework elements");
if (RoutedEvent != null)
associatedElement.AddHandler(RoutedEvent, new RoutedEventHandler(this.OnRoutedEvent));
}
void OnRoutedEvent(object sender, RoutedEventArgs args) { base.OnEvent(args); }
protected override string GetEventName() { return RoutedEvent.Name; }
}
With the end-of-scroll detection in the view model:
public void DoScroll(ScrollChangedEventArgs e)
{
var scrollViewer = e.OriginalSource as ScrollViewer;
if (scrollViewer != null && // Do we have a scroll bar?
scrollViewer.ScrollableHeight > 0 && // Avoid firing the event on an empty list.
scrollViewer.VerticalOffset == scrollViewer.ScrollableHeight && // Are we at the end of the scrollbar?
{
// Do your end-of-scroll code here...
}
}
If someone knows a better way to handle the end-of-scroll event, e.g. doing it in XAML, I'd love to hear it.
Some of the checkboxes on my form should not be able to be checked/unchecked by users. Is there a way for me to cancel the event before the checbox's Check event is triggered?
in winForms it was easy, just
public void cb_BeforeChecked(object sender, EventArgs e){
e.Handled = true;
}
but I cannot find anything like this in WPF...I figure you can probably do it, just need to do something fancy..
Thanks!
Why not just set IsEnabled="False"?
You can set IsHitTestVisible="False" to make it not respond to user clicks. Otherwise you can bind it to a command if viewmodel logic determines whether it is clickable.
<Grid>
<CheckBox IsHitTestVisible="False" Content="I cannot be clicked at all"/>
<CheckBox Command="{Binding DoSomethingCommand}" Content="I can be clicked if DoSomethingCanExecute returns true."/>
</Grid>
In your DataContext (Viewmodel or otherwise):
RelayCommand _DoSomethingCommand = null;
public ICommand DoSomethingCommand
{
get
{
if (_DoSomethingCommand== null)
{
_DoSomethingCommand= new RelayCommand(
param => DoSomething(),
param => DoSomethingCanExecute
);
}
return _DoSomethingCommand;
}
}
public bool DoSomethingCanExecute
{
get
{
return CheckboxShouldBeEnabled();
}
}
public void DoSomething()
{
//Checkbox has been clicked
}
This might be a bit of an overkill, but you could sub-class CheckBox and then override the OnClick() method.
Only setting IsHitTestVisible="False" just takes care of the Mouse, the users can still use the KeyBoard to tab to the CheckBox and change the value.
You should set both IsHitTestVisible="False" and Focusable="False" to disable the KeyBoard as well
You can have the check box disabled, and associate a style with the disabled check box, if the disabled look is a problem. As its already pointed in the previous posts, its good to have different looks for different states.
I have a DataGrid. One of the columns is a template with a CheckBox in it. When the Checked or Unchecked events trigger, (it happens with both) the CheckBox's DataContext is sometimes null, which causes my code to error. It seems to be null most often if the mouse is moving while you press and release the button quickly (it's intermittent).
I listened for changes to the DataContext of the CheckBox by making views:ListenCheckBox (extends CheckBox) and attaching a binding, and it's never set to null, but it is set from null to a Task at times I wouldn't expect, i.e. after the DataGrid has been totally generated and you're checking/unchecking boxes. Immediately after the [un]checked event runs with a null DataContext, I get the notification that shows the DataContext changed from null to a Task, so it appears that when I get a null DataContext, it's because it hadn't actually set the DataContext by the time it ran the Checked/Unchecked event.
Also, I added Tag="{Binding}" to the CheckBox for debugging. The Tag is not null (i.e. it has the proper object) more often than the DataContext, but still not all the time.
Here are the relevant bits of the XAML code:
<navigation:Page.Resources>
<sdk:DataGridTemplateColumn x:Key="DeleteOrPrintSelect" Header="Delete Or Print Notes Selection">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<views:ListenCheckBox IsChecked="{Binding DeleteOrPrintNotesSelection, Mode=TwoWay}" Checked="DeletePrintNotesCheckBox_Changed" Unchecked="DeletePrintNotesCheckBox_Changed" HorizontalAlignment="Center" VerticalAlignment="Center" Tag="{Binding}" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</navigation:Page.Resources>
<sdk:DataGrid x:Name="dataGrid1" Grid.Column="1" Grid.Row="2" AutoGeneratingColumn="dataGrid1_AutoGeneratingColumn">
<sdk:DataGrid.RowGroupHeaderStyles>
[removed]
</sdk:DataGrid.RowGroupHeaderStyles>
</sdk:DataGrid>
And the relevant code behind:
// Create a collection to store task data.
ObservableCollection<Task> taskList = new ObservableCollection<Task>();
[code adding Tasks to taskList removed]
PagedCollectionView panelListView = new PagedCollectionView(taskList);
this.dataGrid1.ItemsSource = panelListView;
}
private void dataGrid1_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyName == "DeleteOrPrintNotesSelection")
{
e.Column = Resources["DeleteOrPrintSelect"] as DataGridTemplateColumn;
}
else
{
e.Column.IsReadOnly = true;
}
}
private void DeletePrintNotesCheckBox_Changed(object sender, RoutedEventArgs e)
{
try
{
var cb = sender as CheckBox;
var t = cb.DataContext as Task;
t.DeleteOrPrintNotesSelection = cb.IsChecked == true;
PagedCollectionView pcv = this.dataGrid1.ItemsSource as PagedCollectionView;
ObservableCollection<Task> taskList = pcv.SourceCollection as ObservableCollection<Task>;
bool anySelected = taskList.Any(x => x.DeleteOrPrintNotesSelection);
this.btnPrint.IsEnabled = anySelected;
this.btnDelete.IsEnabled = anySelected;
}
catch (Exception ex)
{
ErrorMessageBox.Show("recheck", ex, this);
}
}
Any ideas? Thanks in advance.
I found that the problem happened when you double click on the cell and it moved it to the cell editing template. In my case, I didn't have a cell editing template defined, so it used the same cell template, but instead of not changing anything, it apparently decided to make a new check box. I set the column's IsReadOnly property to true, and it fixed it. An alternate solution:
DataContext="{Binding}" (in XAML, or the code equivalent:)
cb.SetBinding(FrameworkElement.DataContextProperty, new Binding());
I'm not sure why this one fixes it, since I thought the default DataContext is {Binding}. Perhaps it's a Silverlight bug, and it gets set in a different order if you define it explicitly instead of leaving it the default.
How do I get a reference to a TextBox that's only defined inside a DataTemplate (assuming that I've just applied this DataTemplate to some cell in a grid).
So far I'm using the sender in the TextBox events to retrieve this.
Thanks,
rui
For getting the reference of a control inside a Data Template, handling the event and then using the sender is one of the available option. There is one more option that you can try:
in .xaml:
<toolkit:DataGrid Name="datagrid" Margin="0,0,0,28" AutoGenerateColumns="False">
<toolkit:DataGrid.Columns>
<toolkit:DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/>
<toolkit:DataGridTemplateColumn Header="Last Name">
<toolkit:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding LastName}"/>
</DataTemplate>
</toolkit:DataGridTemplateColumn.CellTemplate>
</toolkit:DataGridTemplateColumn>
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>
<Button Height="22" VerticalAlignment="Bottom" Click="Button_Click" />
in .xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
InitializeMouseHandlersForVisual(datagrid);
}
public void InitializeMouseHandlersForVisual(Visual visual)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
Visual childVisual = (Visual) VisualTreeHelper.GetChild(visual, i);
if (childVisual is TextBox)
MessageBox.Show("textbox Found");
// Recursively enumerate children of the child visual object.
InitializeMouseHandlersForVisual(childVisual);
}
}
Hope this helps!!
Edit:
if you want to use x:Name then also you need to at least get the ContentPresenter and for getting ContentPresenter you need to go through the element tree. The updates you need to make are:
in .xaml:
<DataTemplate>
<TextBox x:Name="text" Text="{Binding LastName}"/>
</DataTemplate>
in .xaml.cs
public void InitializeMouseHandlersForVisual(Visual visual)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
Visual childVisual = (Visual) VisualTreeHelper.GetChild(visual, i);
ContentPresenter myContentPresenter = childVisual as ContentPresenter;
if (myContentPresenter != null)
{
// Finding textBlock from the DataTemplate that is set on that ContentPresenter
DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;
if (myDataTemplate != null)
{
TextBox myTextBox = (TextBox)myDataTemplate.FindName("text", myContentPresenter);
MessageBox.Show("textbox Found");
}
}
InitializeMouseHandlersForVisual(childVisual);
}
}
Hope this helps!!
Sorry, but you're doing it wrong.
There's no good reason why you should have a reference to elements inside a DataTemplate IMO.
Moreover, there's really no good reason for you to ever register for a Control Event.
As part of the MVVM architecture we started looking at Data and Interactions.
On the Data side - everything is databound to the ViewModel.
On the interactions side - Using ICommands all events are wired up for commands.
So, in your TextBox example - why are you listening to textbox events? Use TwoWay DataBinding to learn when TextBox text change.
In another example in which events are justified, like button.Click? Use Button.Command="{Binding myCommand}" to have commands handle events.
The reason you're running into issues is because you're trying to force a round peg in a square hole.
-- Justin
I agree with Justin.
But if for some reason binding to some property is problematic and you still need reference to a control inside data template in SILVERLIGHT ( above solution is suitable for WPF components ) you can do as follow:
TextBox textBox = null;
if (datagrid.SelectedItem != null)
textBox = datagrid.Columns[1].GetCellContent(datagrid.SelectedItem) as TextBox;
if (textBox != null)
MessageBox.Show(textBox.Text);