I'm working on a Windows Phone 8 C# and SQLite application. I'm really new to Windows Phone applications and usually work with PHP and JS.
There is a LongListSelector, which every item is a Button. Each Button should reference to an ID which is binded from a class of SQLite:
<phone:LongListSelector x:Name="llsRadios" ItemsSource="{Binding Radios}" ItemTemplate="{StaticResource DataTemplate1}"/>
DataTemplate1:
<DataTemplate x:Key="DataTemplate1">
<Grid>
<Button x:Name="btnFoo" Template="{StaticResource ButtonControlTemplate1}" Click="btnFoo_Click" />
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu IsZoomEnabled="True" x:Name="ContextMenu" >
<toolkit:MenuItem x:Name="btnEditFoo" Header="edit" Click="btnEditFoo_Click"/>
<toolkit:MenuItem x:Name="btnDeleteFoo" Header="delete" Click="btnDeleteFoo_Click"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</Grid>
</DataTemplate>
ButtonControlTemplate1:
<ControlTemplate x:Key="ButtonControlTemplate1" TargetType="Button">
<Grid>
<TextBlock x:Name="lblName" Text="Name" />
<TextBlock x:Name="lblCountry" Text="Country" />
</Grid>
</ControlTemplate>
I need to, when a user clicks in one of these buttons, on the event Click, get this ID value that represents a row on SQLite to then run a select * from table where ID = ..., for example.
In JavaScript, I would add a attribute data-id and handle the event like:
this.getAttribute('data-id');
// run an AJAX request
My first idea was to bind to Content of each button the ID, so I could run on btnFoo_click:
Button btn = sender as Button;
var ID = sender.Content;
// Do SQLite select.
But this doesn't seems to be the correct way to do it. Also, further on I have to work with the same concept on ConceptMenus (on hold, two options: Edit and Delete would show. These must do actions to the element which the user was selecting.)
Also I thought that I could access the template of the button and find a hidden element with the same idea of binding a value to its Text or Content attribute. But I couldn't find a way to select element from a template, kind of like jQuery's find: $('.parent').find('.element-i-need');
Sticking with this second idea, how could I search of elements by their names on a ControlTemplate of the clicked button/element?
For the first, do you know that you can get hold of the model via the DataContext? That's the most direct way to do it in a click handler:
Button btn = sender as Button;
var viewmodel = btn.DataContext as MyItemModel;
var ID = viewmodel.ID;
// Do SQLite select.
For the second, you can't access template elements directly from outside the control (there is GetTemplateChild, but that is a protected method). The rough equivalent to the JQuery "find" would be to trace down the visual tree, but that's pretty bad form in XAML, and doesn't always work (not everything is in the visual tree, Popups for example). You could however subclass the Button control, and expose a public property that makes use of GetTemplateChild.
Ideally though, your model logic shouldn't be interacting with the UI at all. When possible it is best to use <Button Command="{Binding SomeCommand}" ... /> where "SomeCommand" is an ICommand implementation, rather than <Button Click="CodeHandler" ... />. That can be easier said than done, though, especially in WP Silverlight without the FindAncestor binding ...
Related
Good Morning!
I have a WPF application that will display a number of different file types based on command line args it receives. It works fine, but I want to go back and refactor it. I have only been a developer for a few years and would like to master MVVM.
I am using an MVVM design package called Stylet. In my PDF view I am using a Telerik RadPdfViewer control to which Telerik has all this binding stuff built in for you. For example, I am binding the right click context menu with the commands "select all" and "copy" using their pre configured command bindings.
I would like to bind the "Document Source" property TO MY viewmodel so I can pass in the paths of documents I want to load. However, the DataContext of the control is bound to Telerik's CommandDescriptors preventing the binding to my viewmodel.
<telerik:RadPdfViewer x:Name="radPdfViewer" Grid.Row="1"
DataContext="{Binding CommandDescriptors, ElementName=radPdfViewer}"
DocumentSource="{Binding PDFDoc}"
telerik:RadPdfViewerAttachedComponents.RegisterFindDialog="True"
HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Stretch"
telerik:StyleManager.Theme="Office_Black" Grid.ColumnSpan="2">
<telerik:RadContextMenu.ContextMenu>
<telerik:RadContextMenu>
<telerik:RadMenuItem Header="Select All"
Command="{Binding SelectAllCommandDescriptor.Command}" />
<telerik:RadMenuItem Header="Copy"
Command="{Binding CopyCommandDescriptor.Command}" />
</telerik:RadContextMenu>
</telerik:RadContextMenu.ContextMenu>
</telerik:RadPdfViewer>
public class PDFViewModel
{
private string _pdfDoc;
public string PDFDoc
{
get
{
return _pdfDoc;
}
set
{
_pdfDoc = value;
}
}
public PDFViewModel()
{
PDFDoc = #"t:\share\large.pdf";
}
}
I see two choices
I break Telerik's prebuilt command bindings and figure out how to bring the select all and copy functions to my viewmodel.
Stylet has an s:Action function where I can call a method where I can load the document into the RadPdfViewer control using C#. I would need to somehow get control of the gui control in the method of my viewmodel and I am not sure how to do that.
Is there a better way? A little nudge in the right direction would be greatly appreciated.
Jason Tyler's reply got me going in the right direction. Thank you!
So because I am using a ViewModel first pattern, I did not need to specify the DataContext of the user control like I thought...Its already set.
However, his suggestion of binding using the relative source and researching on how to do this (I have never used RelativeSource before..I am kinda new to this stuff) I came across this Stack post
How do I use WPF bindings with RelativeSource?
A Jeff Knight Posted a diagram of how ancestor binding works.
Using that, I was able to figure out the syntax and my document came right up and I can still use the right click context menu items that are bound to Telerik. So now my Xaml looks like this note how the Document source binding has changed.
<telerik:RadPdfViewer x:Name="radPdfViewer" Grid.Row="1"
DataContext="{Binding CommandDescriptors, ElementName=radPdfViewer}"
DocumentSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=DataContext.PDFDoc}"
telerik:RadPdfViewerAttachedComponents.RegisterFindDialog="True"
HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Stretch"
telerik:StyleManager.Theme="Office_Black" Grid.ColumnSpan="2">
<telerik:RadContextMenu.ContextMenu>
<telerik:RadContextMenu>
<telerik:RadMenuItem Header="Select All"
Command="{Binding SelectAllCommandDescriptor.Command}" />
<telerik:RadMenuItem Header="Copy"
Command="{Binding CopyCommandDescriptor.Command}" />
</telerik:RadContextMenu>
</telerik:RadContextMenu.ContextMenu>
</telerik:RadPdfViewer>
I have a resource dictionary combining a number of datatemplates. I'm including this resource dictionary as a ResourceDictionary.MergedDictionaries in my Page.Resources. One of my datatemplates is a ListView and while the item source and item click is working correctly, a separate button on the ListViewItem, set in the datatemplate, is not calling my click method. Im unsure about setting this up correctly.
This click method is defined in the code behind class the defines the pages Xaml including the resource dictionary and using my datatemplate for ListViewItems.
Dictionaries
DataTemplates.xaml <- ListView template here with a button click defined in the page cs, i.e. Click="MyPages_ClickMethod"
Pages
MyPage.xaml
MyPage.xaml.cs <- click method defined here, MyPages_ClickMethod()
Here is how I am setting up the button in the datatemplate:
<Button Tag="{Binding id}" Click="MultiShareSelectFileButton_Click" Background="Transparent" Visibility="{Binding multiShareSelected, Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=Inverted, Mode=OneWay}">
<Image Width="27" Source="ms-appx:///Assets/sharePlusIcon#2x.png" VerticalAlignment="Center" HorizontalAlignment="Center">
</Image>
</Button>
Is it possible to do this without using ICommand?
Something like: Click="{x:Bind Path=pages:ProductPage.MultiShareSelectFileButton_Click}", but this is complaining that MultiShareSelectFileButton_Click should be static
I'll get right to it. Here is the issue,
Your DataTemplate is in a resource dictionary. The resource dictionary is made for styles and converters if I may. Putting the DataTemplate in a resource dictionary is not recommended.
Why isn't it recommended?
The reason is straight, resource dictionaries are used to put global data. For ex: a control style that you might want to be available through out your app or your converters which are being used frequently.
This is because generally you would define the resource dictionary in your app.xaml which runs when your splashscreen appears.
Now if you have a lot of stuff (DataTemplates, Styles, Converters) all defined into resource dictionaries that are merged in <Application.ResourceDictionary> part of app.xaml, it's gonna have a significant impact on your app launch time, which will spoil your user's experience.
What's advised?
It's advised to keep your converters and styles not global unless you need them everywhere. For example: If you have a BoolToVisibilityConverter or a CustomRoundButtonStyle which you use only on one page/userControl out of 4. Then it doesn't make sense to load the style or converter for the other 3 Pages. So you should declare them in <Page.Resources> instead.
Same for your DataTemplate why declare it globally if you want to use it just once. Rather declare it to your <Page.Resources>. Your problem will be solved immediately as your Page will have a code-behind, so your xaml will know where to look for the method. That's where things are going wrong.
But in-case you have a single DataTemplate to be used on all your Views below is your solution:
Your Solution:
In-case you have to use it in a resource dictionary, use {x:Bind} and x:DataType="Models:YourDataContextModel" to bind your DataTemplate to your model. this ways your xaml will know exactly where to look for the method on click.
Below is a sample of it:
<DataTemplate x:Key="HelloTemplate" x:DataType="yourDefinedNameSpace:YourModel">
<Button Click="{x:Bind GoFetchData}"/>
</DataTemplate>
Where YourModel exists in a namespace defined as "yourDefinedNameSpace" in xaml and it contains a method of signature: internal void GoFetchData()
I hope this helps. Feel free to use the comments section if you have any doubts
I found that it was also necessary to specify ClickMode="Press" in Xaml.
<Button Content="" Focusable="True" FontFamily="Segoe UI Symbol" FontSize="16" Background="{StaticResource HeroLightGray}" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center"
ClickMode="Press"
Command="{Binding DataContext.CopyMetadataSourceAsync, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" CommandParameter="{Binding .}" />
I do not recall having to do this in the past, but after spending hours troubleshooting, converting RelayCommand to IAsyncCommand, etc. this is the only thing that worked. In fact, I couldn't even get a regular code-behind "Click event" method to fire unless I included that ClickMode="Press"!
I am fairly new to WPF and have two questions?
Question #1:
From my XAML snip below, by button "btnRed" word's fine with my code behind. When the button is clicked, the proper view is display. However, how does one perform the same thing "programmatically"? Hence, my next question.
Question #2:
I am not sure how to make a "textbox" and "button" work together to perform the same action. What I'm trying to do is this. (1) I would like the textbox to be linked to the "DataContext" of the button, "btnDisplayView". (2) so when I type in, say, "RedView" into the textbox and click the button, the correct view is displayed.
My long term goal is to have a database, with a couple of tables. A table for "MenuItems" and a table for "Views". Instead of using buttons, I'll use the menu control. Then once a menu item is selected, it would display the correct view.
But for now, I'm startings small and trying to keep it simple:
--------- WPF - XAML START ---------------------------
<StackPanel Grid.Column="0" Orientation="Vertical">
<TextBox x:Name="txtDisplayView" Height="23" Margin="5" TextAlignment="Center"/>
<Button x:Name="btnDisplayView" Content="Display" Margin="5" Click="btnDisplay_Click"/>
<Button x:Name="btnRed" Content="Red" Margin="5" DataContext="RedView" Click="Red_Click"/>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Vertical">
<ContentControl Margin="5" Content="{Binding}"/>
</StackPanel>
-----------WPF - XAML END -------------------------
If someone could show me how to get this to work, it would help me move my project in the right direction and would be greatly appreciated.
What you need here is:
Create a property in your DataContext that represents the selected item
Bind that property to your TextBox element
Now, you have two options. One is "WPF Friendly" and the other is more Windows Forms-ish:
Create a command (take a look at this article) that reads a parameter, which will be binded to the property you created before
On the Click event, you can read the binded property value
I personally prefer the first solution. Why? Because when you change it to a Menu, for example, your work will be only to populate the menu with your list items (the MenuItem class also has a Command property, so the implementation is the same as with a Button). You will only need to change the source!
I have a ListView that looks like this, that controls which tab in my application that is opened.
<ListView Grid.ColumnSpan="2" Grid.Row="1" SelectedItem="{Binding Path=SelectedSubstanceName}" Name="listView" ItemsSource="{Binding Path=Substances}" HorizontalAlignment="Stretch" Margin="2" VerticalAlignment="Stretch">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Lägg till" Command="{Binding AddSubstanceCommand}"/>
<MenuItem Header="Ta bort" Command="{Binding RemoveSubstanceCommand}"/>
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I use the SelectedSubstanceName property to detect which tab to open, or switch to, if it's already open.
The property looks like this:
private SubstanceName selectedSubstanceName;
public SubstanceName SelectedSubstanceName
{
get
{
return selectedSubstanceName;
}
set
{
selectedSubstanceName = value;
OnPropertyChanged("SelectedSubstanceName");
if (selectedSubstanceName != null)
{
if (!Tabs.Any(t => t.Identify(selectedSubstanceName.SubstanceNameID, typeof(SubstanceTabsViewModel))))
AddTab(selectedSubstanceName);
else
SelectedTab = Tabs.First(t => t.Identify(selectedSubstanceName.SubstanceNameID, typeof(SubstanceTabsViewModel)));
}
}
}
The case I'm not able to cover is when the user clicks "someSubstance", the corresponding tab is opened, the user closes it, and "someSubstance" is still selected. If the user wants to open it again, he has to select some other substance (which will then be opened), and then click "someSubstance" again. Is it possible to trigger the property even when clicking the same ListViewItem?
I know I could add an event on double-click, but ideally, I want to avoid both events and double-clicks.
I think the problem is that after clicking an item the first time the list's SelectedItem gets set. After clicking the same item the second time SelectedItem won't change because it is already set to that item. What you should do is set the SelectedItem to null after handling the click.
Try to unselect all Items in your ListView after the tab is closed.
YOURLISTVIEW.UnselectAll();
So the next time someone selects an Item there will be a change.
You don't actually want to use the ListView class, but instead simply use the ItemsControl, since it is the most basic way of representing a sequence of elements, but without the extras such as SelectedItem, SelectedValue, etc. that any class deriving from Selector has.
From there, it's merely a matter of how to represent each item in the ItemsControl. The behavior you want is to know when a specific item has been clicked on, which would make the Button class a good candidate, since it handles click behavior through an ICommand interface. Obviously, since you know about DataTemplates and styling in general, you should already know that you can customize how the button looks (visually) without sacrificing the actual behavior (click-handling).
<ItemsControl ItemsSource="{Binding Path=Substances}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource SomeStyleToChangeItsLook}"
Command="{Binding Path=SelectSubstanceCommand}"
CommandParameter="{Binding}"
Content="{Binding Path=Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
public ICommand SelectSubstanceCommand { get; private set; }
private void SelectSubstance(object parameter)
{
// Add the substance that was "clicked" on here however you want to do it.
}
Keep in mind I don't know what framework you are using, so I just gave a general example of how the Command code might look in your view-model. The key to MVVM and using WPFs awesome UI is to always think of what behavior you want and which controls offer that behavior. Ignore how they actually look because that can be changed without losing that behavior.
Recently I had been looking for a way to make the tabs in a TabControl editable and came across This example on telerik's website. That did exactly what I wanted but it got me thinking about a similar usage for buttons. I was wondering if it would be possible to use something like that and make a button that would show a textbox instead of the content presenter when say, you right click the button? I tried to make something like this work but so far have only ended up with a blank button.
<Button x:Name="SB" Height="222" Width="222" Click="SB_Click">
<ContentControl.ContentTemplate>
<DataTemplate>
<local:SuperButton Content="{Binding Path=x, Mode=TwoWay}"/>
</DataTemplate>
</ContentControl.ContentTemplate>
</Button>
Where x is a string variable and using the code behind from the link above (with a class name change, of course).
edit: This button will be in an itemscontrol, so I don't think naming the inner elements in xaml will work, but I do like the ease of Wolfgang's answer.
The WPF Content Model is really flexible and allows literally anything inside anything.
This is perfectly valid XAML:
<Button>
<TextBox/>
</Button>
Or even:
<Button>
<MediaElement Source="C:\Videos\WildLife.wmv"/>
</Button>
You can simply host a (e.g.) label (TextBlock) with the text AND a TextBox inside the Button and set their Visiblity properties.
That way, if you right click the button, the TextBox shows up.
<Button>
<Grid>
<TextBox Text=normal button caption" x:Name="label" />
<TextBox
x:Name="textbox"
Text="visible on right click"
MouseRightButtonDown="HandleRightClick"/>
</Grid>
</Button>
And then in your C# code create an event handler to set the Visiblity correctly.
void HandleRightClick(object sender, MouseButtonEventArgs e)
{
label.Visibility = Visibility.Collapsed;
textBlock.Visibility = Visibility.Visible;
}