WPF DataGridTemplateColumn.HeaderTemplate issue - c#

Have an issue with getting access to a button that is embedded in a
DataGridTemplateColumn.HeaderTemplate. The button is created fine and can attach an event to the button. All works well, except when attempting to get access from the code behind, Ex:btnbutton.IsEnabled = false; Returns an error and said the object does not exist. I have tested this with various version of .NET up to 4.5 and get the same results, btnButton does not exist.
I have done this same with a ListView object and it works fine.
<DataGrid Name="dgTest1" ItemsSource="{Binding} AutoGenerateColumns="False"
<DataGrid.Columns>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Name="btnButton" Content="One Button" WinWidth="100"></Button>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
</DataGrid.Columns>
</DataGrid>
Code Behind: btnButton.IsEnabled = false;
Is there a way to gain access to a button in a DataGrid Template Header?

You can try something like this :
var aButton = templateName.ChildrenOfType<Button>().FirstOrDefault( b => b.Name == "btnButton" );

You can not access template control like that, you need to apply template and then find them
You need to call apply template first on DataGrid header and then
var button = dgTest1.template.findname("btnButton", dgTest1)

I believe the proper way here would be to bind your button to a property on the view model:
<Button IsEnabled="{Binding IsButtonEnabled}" Content="One Button"/>
or to a command (you can implement your own command, create a property IsEnabled in it, and return this property's value from the CanExecute() method). Let me know if you need more details here.

Related

Copy String of a ListViewItem into a TextBox

So I'm working on a calculator, basically a copy of the Windows Version, as a training excercise. I have implemented a History of past calculations, and I was asked to transform this history from TextBox to Listview.
What I want to do is copy one of the past calculations back into the Calculator TextBox when I click on it, just like in the Windows Calculator.
My ListViewCode:
<ListView Grid.Column="0" Grid.Row="1" Foreground="#616161" Name="history" Background="Transparent"
HorizontalAlignment="Stretch" BorderThickness="0" Margin="10,10,10,0">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="MouseLeftButtonDown" Handler="RetrievePastCalculation" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
And this is the RetrievePastCalculation method, but it doesn't work, nothing happens when I click on a ListViewItem. I'm new to WPF by the way.
private void RetrievePastCalculation(object sender, MouseButtonEventArgs e)
{
innerTextBox.Text = history.SelectedItems.ToString();
}
This is where I add items to the ListView I think, it's the Equal button method:
private void ButtonEquals_Click(object sender, RoutedEventArgs e)
{
Calculator calculate = new Calculator();
textBox.Text = calculate.Calculate(innerTextBox.Text);
history.Items.Add(innerTextBox.Text + "=" + textBox.Text);
innerTextBox.Clear();
}
history.SelectedItems is a collection, so calling ToString on it won't give you anything other than the name of the type. If you try it in the debugger (which you should), you'll see that it returns System.Windows.Controls.SelectedItemCollection. Now, at this point you can either fix your issue one of two ways: you can continue to use your current event-based approach, or you can use binding.
Events
With events, you can hook a handler to the Selected event for each ListItem that you add to the list:
private void ButtonEquals_Click(object sender, RoutedEventArgs e)
{
Calculator calculate = new Calculator();
textBox.Text = calculate.Calculate(innerTextBox.Text);
var item = new ListViewItem();
item.Content = innerTextBox.Text + "=" + textBox.Text;
item.Selected += HistoryItem_Selected //hooks the handler to the 'Selected' event
history.Items.Add(item);
innerTextBox.Clear();
}
then define the handler itself:
private void HistoryItem_Selected(object sender, RoutedEventArgs e)
{
// here 'sender' will be the ListItem which you clicked on
// but since it's an object we need to cast it first
ListViewItem listItem = (ListViewItem)sender;
// now all that's left is getting the text and assigning it to the textbox
innerTextBox.Text = listItem.Content.ToString();
}
Binding
Binding is much simpler as far as the amount of code is concerned, but has a steeper learning curve. Here, instead of setting the TextBox.Text property directly, we will specify a binding expression. This means that the value will always be the same as that of the bound expression.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<ListView Grid.Column="0" Grid.Row="0" Name="history" />
<TextBox Text="{Binding ElementName=history, Path=SelectedItem.Content}" />
<Button Name="ButtonEquals" Content="equals" Click="ButtonEquals_Click"/>
</StackPanel>
</Grid>
</Window>
I've run this in a new WPF project and it works as expected: the text box displays whatever text is in the clicked item from the list.
One thing to note is that both solutions assume that you are assigning strings to the ListViewItem Content. As you may know, you can assign other controls or any object to the Content property of a UI Control (ListViewItem inherits from Control). That's why the ListViewItem.Add method takes an argument of type object and is not restricted to one of type string. If you assigned anything other than a string in your button click event handler, both of the two cases above would likely break.
You could bind the value of the TextBox to the SelectedItem of the ListView. Here's an example:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<ListView Grid.Column="0" Grid.Row="0" Foreground="#616161" Name="history" Background="Transparent"
HorizontalAlignment="Stretch" BorderThickness="0" Margin="10,10,10,0">
<ListViewItem>Calc1</ListViewItem>
<ListViewItem>Calc2</ListViewItem>
</ListView>
<TextBox Text="{Binding ElementName=history, Path=SelectedItem.Content}" />
</StackPanel>
</Page>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
<RowDefinition Height="100"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListView Grid.Column="0" Grid.Row="0" Foreground="#616161" Name="history" BorderThickness="1,1" Height="50" Width="200" SelectionChanged="history_SelectionChanged">
<ListViewItem>
<TextBlock> A ListView</TextBlock>
</ListViewItem>
<ListViewItem>
with several
</ListViewItem>
<ListViewItem>
items
</ListViewItem>
</ListView>
<TextBox Grid.Row="1" Text="{Binding ElementName=history,Path=SelectedValue.Content}"
BorderThickness="1,1" Height="50" Width="200" />
</Grid>
It's better if you do it using XAML code. try to select item 0 and 1 to see the difference and understand how listboxworks.
now replace the text of textbox binding with following:
Text="{Binding ElementName=history,Path=SelectedValue.Content.Text}"
and seee the output for item 0. Hopefully you'll achieve desired output with a lot less effort.
Now that you have explained the whole problem i think you need to implement a converter in the text binding of TextBox. like below text
Text="{Binding ElementName=history,Path=SelectedValue.Content.Text,Converter={StaticResource mytextconverter}}"
and write down a logic to extract a part of text on the basis of '=' char. It's very easy to write a converter class. to write a converter follow the below link:
WPF Converter example

Element is already the child of another element when doesnt exist?

I'm trying to programatically create a button flyout, within my XAML I have:
<Page.Resources>
<Button x:Key="LaunchFlyout" Content="LAUNCH">
<Button.Flyout>
<Flyout Placement="Top">
<Grid Width="200" Height="200">
<StackPanel>
<Rectangle Fill="Red" Width="100" Height="100" />
<Rectangle Fill="Green" Width="100" Height="100" />
</StackPanel>
</Grid>
</Flyout>
</Button.Flyout>
</Button>
</Page.Resources>
Nested within grids I have:
<Grid x:Name="launchBtn_grid" Grid.Column="1">
</Grid>
And then in my code within the Page_Loaded method I have:
bool hasContainer = localSettings.Containers.ContainsKey("appStatus");
if (!hasContainer) {
Button button = (Button)this.Resources["LaunchFlyout"];
launchBtn_grid.Children.Add(button);
}
else {
Button button = new Button();
button.Content = "LAUNCH";
button.Click += launch_btn_Click;
launchBtn_grid.Children.Add(button);
}
When I debug this, it reaches the IF statement and reaches this line launchBtn_grid.Children.Add(button); and then I get this error Element is already the child of another element.
Does anyone understand why? I have already looked and they dont already exist so I don't understand why it is giving me this error. Does anyone know what I am doing wrong?
I'm not sure in what context/use case your are doing that, but it feels weird to me to have an actual control as a Resource (not a DataTemplate, Style, etc).
If you only want to have 1 button of the 2 different template, why not switch Visibility on the 2 instead of loading controls from your code behind ?
Going forward with the idea, just add both buttons in the Grid within your XAML and switch their Visibility according to the setting you read.
There is a BooleanToVisibilityConverter within the framework to help you with this.

How to load UI on navigated page(2nd page) according to the grid clicked on first page

I am working on a windows store 8.1 app, I have added Grids in MainPage.xaml using List in MainPage.xaml.cs
MainPage.xaml
<GridView Margin="20" x:Name="main" SelectionMode="None" IsItemClickEnabled="True" ItemClick="main_ItemClick">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Background="Red" Width="250" Height="200">
<Grid.RowDefinitions>
<RowDefinition Height="150"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Stretch="UniformToFill" Source="{Binding ImageLocation}"/>
<TextBlock Text="{Binding Title}" Grid.Row="1" FontSize="28" />
<TextBlock Text="{Binding SubTitle}" Grid.Row="2" FontSize="16" />
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
MainPage.xaml.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
List<data> myList = new List<data>();
myList.Add(new data()
{
ImageLocation = #"Assets/network.png",
iName = "NetWork",
SubTitle ="Network",
Title = "Network"
});
myList.Add(new data()
{
ImageLocation = #"Assets/fb.png",
iName = "Facebook",
SubTitle = "Facebook",
Title = "Facebook"
});
main.ItemsSource = myList;
}
private void main_ItemClick(object sender, ItemClickEventArgs e)
{
Frame.Navigate(typeof(ListView));
}
I want that when someone click on any of the grids, a TextBlock in ListView page show which grid was clicked in MainPage .
This will be a challenge to explain without showing you in code, but here goes...
Hopefully you have created two pages so far. MainPage.xaml that holds your GridView. And a DetailsPage.xaml that will have the layout to show one item.
In the code-behind of MainPage.xaml, like you have in your sample code, you handle the ItemCLick of the GridView, but you want to get the Id of the item clicked, not the item itself. The reason for this is that you want to pass a string, and not a complex object.
In your handler, the event args (e) has a property called ClickedItem that will be the item you are binding to. Let's pretend it's a UserObject you are binding to. In your handler do something like this:
var user = e.ClickedItem as UserObject;
this.Frame.Navigate(typeof(DetailPage), user.Id.ToString());
So, what's happening here? Almost the same code you had before. Except you are navigating to the type of the second page instead of anything else. You are also passing in (the second argument in the Navigate method) the exact record you want to show.
Then in your DetailPage.xaml code-behind you ned to override the OnNavigatedTo method. This method is what is invoked when the Navigation framework directs to the page. It's has a NavigationPararmeter passed to it that you can use to extract the key you passed.
I think it's actually args.Parameter you want to use. You can parse it to an integer and use that to fetch the individual record you have somehow in memory in your application.
var id = int.Parse(args.Parameter);
var user = YourFactory.GetUser(id);
The reason I shifted from this is how you do it to "I think this is how it works" is because although the basic framework operates like this, most developers do not use it like this. Most developers implement something like Prism.StoreApps which introduces not only a lightweight MVVM framework, but also a sophisticated NavigationService that lets you inject parameters directly into an auto-associated view model.
But based on the simplicity of your question, try not to pay attention to that last bit. I explained the basic workflow using the in-box framework. It works just fine, and it will get the job done. When you are ready to write a more advanced implementation you can investigate Prism.StoreApps
More info: http://msdn.microsoft.com/en-us/library/windows/apps/xx130655.aspx
Best of luck!

DataGrid CellTemplate TabNavigation

I assume this has been asked before but I couldn't find it, so let me know if it's a duplicate found with other verbiage or something.
The problem is with an SL4 DataGrid which contains multiple CellTemplate's including Checkbox, Button etc. By default it will only tab through these elements on the first row. If I set TabNavigation="Cycle" it will tab through all the elements, but it doesn't move on to the next elements and instead just re-iterates the tabbing through the same DataGrid.
If I set it to Once then again it will only tab through the first row....and SL4 doesn't appear to have a Continue option to move onto the next object once it reaches the edge.
I'm looking for just an easy way to take the equivalent of TabNavigation="Cycle" except when it reaches the last tab-able element in the DataGrid then it moves on to the next thing in the tree instead of just tabbing back to the first element in the DataGrid again. What am I missing here?
There doesn't seem to be a native way to do this in Silverlight, here is a list of supported key strokes in the data grid control: http://msdn.microsoft.com/en-us/library/cc838112(v=vs.95).aspx
I was able to fake it by using a KeyDown event and checking for Tab then setting the editing cell manually:
<Grid x:Name="LayoutRoot" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="Some text" />
<sdk:DataGrid Grid.Row="1" ItemsSource="{Binding People}" AutoGenerateColumns="False" KeyDown="DataGrid_KeyDown">
<sdk:DataGrid.Columns>
<sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding FirstName}" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
</sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding LastName}" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
<TextBox Grid.Row="2" Text="Some more text" />
</Grid>
private void DataGrid_KeyDown(object sender, KeyEventArgs e)
{
DataGrid dg = (DataGrid)sender;
ObservableCollection<Person> items = dg.ItemsSource as ObservableCollection<Person>;
if (e.Key == Key.Tab && dg.SelectedIndex < items.Count -1)
{
dg.SelectedIndex++;
dg.CurrentColumn = dg.Columns[0];
dg.BeginEdit();
var cell = dg.CurrentColumn.GetCellContent(dg.SelectedItem);
}
}
I had some exp wth SL4 long time back. I will give your problem a try:
See the property you set to get your desired behavior won't work. It will be the Microsoft way only, thus alternative is to write your own code to achieve the required behavior.
My idea is to attach the following event to each datagrid cell:
private void DataGridCell_KeyDown(object sender, KeyEventArgs e)
{
if (keypressed == 'TAB' && last cell of the datagrid)
{
e.handled=true;
int tabIndex = dg.TabIndex;
tabindex++;
Control control = GetControl(tabIndex); // You can use visual tree in the method to get it
control.select();
control.focus();
}
}
My apologies I have written pseudo instead of real code, since it will take time for me to recall the code I use to do in SL.
Hope this solution works for you both way, when you tab out of data-grid and reverse tab in to the data-grid.

WPF Hyperlink Child

I have a WPF Hyperlink that I am able to click and get its NavigateUri property just fine. However, I want to be able to bundle some additional information with the Hyperlink so I can process it in my event handler. This is how it looks right now:
<TextBlock Grid.Row="0">
<Hyperlink ToolTip="{Binding Path=Contact.ToolTipPersonalEmail}"
Name="ContactHyperlink" Foreground="#FF333333"
RequestNavigate="HandleContactEmailClicked"
NavigateUri="{Binding Path=Contact.Email}"
>
<TextBlock Text="{Binding Path=Contact.Fullname}" Width="Auto"
HorizontalAlignment="Stretch"
TextTrimming="CharacterEllipsis"/>
<TextBlock Text="{Binding Path=Data1}" Name="data1" Visibility="Collapsed" />
<TextBlock Text="{Binding Path=Data2}" Name="data2" Visibility="Collapsed" />
</Hyperlink>
</TextBlock>
Basically, in my event handler, I want to be able to access the data inside the two textblocks that have visibility = "Collapsed" (data1 and data2). I liken this to "hidden" data in an HTML form.
I tried messing with the "Inlines" property of Hyperlink but that's not working, and since this is inside a DataTemplate I can't access data1 and data2 by name in my code.
Any ideas?
Thanks.
creating textblocks to hold that data is somewhat... overkill. I'd go with one of these two options:
use databinding, to place a specific
object into the hyperlink, then to
get it back, all you need to do, is
access the DataContext of the
hyperlink,and it will provide you
the class which holds data1 and
data2
attach the object which
populates data1 and data2 into the
Hyperlink's TAG attribute
In your event handler you can do something like this:
ContentPresenter presenter = (ContentPresenter)sender.TemplatedParent;
DataTemplate template = presenter.ContentTemplate;
TextBlock textBlock = (TextBlock)template.FindName("data1", presenter);
Probably not the prettiest way, but it works for me.

Categories

Resources