I have a simple dynamic Gridview:
<GridView x:Name="MainGridStations" ItemsSource="{x:Bind Stations}" IsItemClickEnabled="True" ItemClick="GridView_ItemClick">
<GridView.ItemTemplate>
<DataTemplate x:DataType="local:Station">
<Grid>
<Grid Background="White" HorizontalAlignment="Center" Width="300" Height="200" VerticalAlignment="Center">
<Image x:Name="ImageStation" Source="{Binding ImageURL}"/>
<Grid Background="#e4f0fc" Height="65" VerticalAlignment="Bottom" Opacity="0.8">
<TextBlock x:Name="StationName" Text="{Binding Name}" FontWeight="Bold" Foreground="#2c9a8b" HorizontalAlignment="Center" />
</Grid>
</Grid>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
Now i wanna use native ads with this gridview. In the code for the native ads i need to register the adcontainer that holds the ad for click events and such.
It will always be the first child of the gridview, meaning the first grid container within the gridview.
I've trie this:
nativeAd.RegisterAdContainer(MainGridStations.ContainerFromIndex(0));
Here it gives me the error, that it can't convert from Windows.UI.Xaml.DependencyObject to Windows.UI.Xaml.FrameworkElement
Basically it is expecting a direct name of a xaml control which i can't provide.
How can i overcome this?
Here it gives me the error, that it can't convert from Windows.UI.Xaml.DependencyObject to Windows.UI.Xaml.FrameworkElement
RegisterAdContainer method to register the UI element that acts as a container for the native ad; this is required to properly track ad impressions and clicks.
And available parameter of RegisterAdContainer is FrameworkElement. But the value of MainGridStations.ContainerFromIndex(0) is DependencyObject. So it will throw exception.
If you want to display the Native Ad in the GridView first item, you could get the first item when it rendered like the following.
You need to inherit GridView and override MeasureOverride, ArrangeOverride method to get the item, then get child with Visual Helper.
class MyGridView : GridView
{
private const string ImageStation = "ImageStation";
private const string StationName = "StationName";
private const string RootLayout = "RootLayout";
private bool isFirst;
NativeAdsManagerV2 myNativeAdsManager = null;
string myAppId = "d25517cb-12d4-4699-8bdc-52040c712cab";
string myAdUnitId = "test";
public MyGridView()
{
myNativeAdsManager = new NativeAdsManagerV2(myAppId, myAdUnitId);
myNativeAdsManager.AdReady += MyNativeAd_AdReady;
myNativeAdsManager.ErrorOccurred += MyNativeAdsManager_ErrorOccurred;
myNativeAdsManager.RequestAd();
}
private void MyNativeAdsManager_ErrorOccurred(object sender, NativeAdErrorEventArgs e)
{
}
private void MyNativeAd_AdReady(object sender, NativeAdReadyEventArgs e)
{
NativeAdV2 nativeAd = e.NativeAd;
AdTitle.Text = nativeAd.Title;
Adimage.Source = nativeAd.AdIcon.Source;
nativeAd.RegisterAdContainer(AdContainer);
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
if (this.IndexFromContainer(element) == 0)
{
isFirst = true;
}
else
{
isFirst = false;
}
base.PrepareContainerForItemOverride(element, item);
}
protected override Size MeasureOverride(Size availableSize)
{
return base.MeasureOverride(availableSize);
}
private Image Adimage;
private TextBlock AdTitle;
private Grid AdContainer;
protected override Size ArrangeOverride(Size finalSize)
{
if (isFirst)
{
var item = this.ContainerFromIndex(0);
AdContainer = MyFindGridViewChildByName(item, RootLayout) as Grid;
Adimage = MyFindGridViewChildByName(item, ImageStation) as Image;
AdTitle = MyFindGridViewChildByName(item, StationName) as TextBlock;
isFirst = false;
}
return base.ArrangeOverride(finalSize);
}
public static DependencyObject MyFindGridViewChildByName(DependencyObject parant, string ControlName)
{
int count = VisualTreeHelper.GetChildrenCount(parant);
for (int i = 0; i < count; i++)
{
var MyChild = VisualTreeHelper.GetChild(parant, i);
if (MyChild is FrameworkElement && ((FrameworkElement)MyChild).Name == ControlName)
return MyChild;
var FindResult = MyFindGridViewChildByName(MyChild, ControlName);
if (FindResult != null)
return FindResult;
}
return null;
}
}
Related
I'm writing a UWP photo viewer app that has a custom control that contains a Viewbox and has custom ManipulationEvents, inside a FlipView. I want to make it so that when you are zoomed out all the way you can swipe to flip, but still able to zoom in. That's where my problem is.
When I have the viewbox set to anything except ManipulationMode = ManipulationModes.System, dragging on the viewbox does not trigger the FlipView. The problem is I want to be able to still zoom in on the image when in zoom level 1.
Basically I'd like to set something that looks like: ManipulationMode="Scale, System", where everything that's not scale would be bubbled up. Or even trigger this in code-behind.
How would I accomplish this?
Here is the basis of what I am doing:
CustomControl.xaml
<UserControl ...>
<Grid>
<ScrollViewer ...
ManipulationMode="System">
<Viewbox ManipulationMode="TranslateX, TranslateY, Rotate, Scale"
ManipulationStarted="Viewbox_OnManipulationStarted"
ManipulationDelta="Viewbox_ManipulationDelta">
<ContentControl Content="{Binding ElementName=MyControl, Path=ViewboxContext}" />
</Viewbox>
</ScrollViewer>
</Grid>
CustomControl.xaml.cs
...
void ViewboxHost_OnManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
{
...
}
void ViewboxHost_OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
if (IsAtMinZoom && e.Delta.Scale <= 1)
{
e.Handled = false;
} else {
e.Handled = true;
return;
}
...
}
MainPage.xaml
<Page ...>
<Grid>
<FlipView>
<FlipView.Items> <!-- These will later be added view ItemsSource -->
<FlipViewItem>
<controls:CustomControl>
<Image src="..." />
</controls:CustomControl>
</FlipViewItem>
<!-- More of these guys --->
</FlipView.Items>
</FlipView>
</Grid>
If I have correctly understand your question, you want to show a picture in each FlipViewItem, and this picture can be zoomed. When FlipView works on mobile, it can be swiped between items, and you want to make sure, this FlipView can only be swiped when the iamge's ZoomFactor = 1.
Here I wrote a sample to solve this problem, I didn't use any ViewBox and UserControl here, if you need to use them, you can change the DataTemplate in my sample:
<FlipView x:Name="flipView" ItemsSource="{x:Bind mylist}">
<FlipView.ItemTemplate>
<DataTemplate>
<ScrollViewer ZoomMode="Enabled" MinZoomFactor="1" MaxZoomFactor="4" PointerPressed="ScrollViewer_PointerPressed">
<Image Source="{Binding ImageSource}" Stretch="Uniform" />
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
code behind:
private ObservableCollection<MyFlipViewItem> mylist = new ObservableCollection<MyFlipViewItem>();
public MainPage()
{
this.InitializeComponent();
this.Loaded += MainPage_Loaded;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
mylist.Clear();
mylist.Add(new MyFlipViewItem { ImageSource = "Assets/1.jpeg" });
mylist.Add(new MyFlipViewItem { ImageSource = "Assets/1.jpeg" });
mylist.Add(new MyFlipViewItem { ImageSource = "Assets/1.jpeg" });
mylist.Add(new MyFlipViewItem { ImageSource = "Assets/1.jpeg" });
}
private ScrollViewer flipviewscrollviewer;
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
flipviewscrollviewer = FindChildOfType<ScrollViewer>(flipView);
}
public static T FindChildOfType<T>(DependencyObject root) where T : class
{
var queue = new Queue<DependencyObject>();
queue.Enqueue(root);
while (queue.Count > 0)
{
DependencyObject current = queue.Dequeue();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(current); i++)
{
var child = VisualTreeHelper.GetChild(current, i);
var typedChild = child as T;
if (typedChild != null)
{
return typedChild;
}
queue.Enqueue(child);
}
}
return null;
}
private void ScrollViewer_PointerPressed(object sender, PointerRoutedEventArgs e)
{
var itemscrollviewer = sender as ScrollViewer;
if (itemscrollviewer.ZoomFactor != 1)
{
flipviewscrollviewer.HorizontalScrollMode = ScrollMode.Disabled;
}
else
{
flipviewscrollviewer.HorizontalScrollMode = ScrollMode.Enabled;
}
}
The MyFlipViewItem class:
public class MyFlipViewItem
{
public string ImageSource { get; set; }
}
Here in my sample you can see that I used PointerPressed event to detect if the manipulation is started. For the reason, you can refer to my other case: ListView ManipulationCompleted event doesn't work on phone.
I'm working with Xamarin.Forms in a PCL project.
I have a page/screen where there are an ListView control. I have created a custom DataTemplate for ViewCell.
This ViewCell has different controls: some Labels, one Button and also a Entry.
<ListView x:Name="lvProducts" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout BackgroundColor="#FFFFFF" Orientation="Vertical">
<Grid Padding="5">
...
<Button Grid.Row="0" Grid.Column="1" Text="X"
CommandParameter="{Binding MarkReference}"
Clicked="DeleteItemClicked" />
...
<StackLayout Grid.Row="2" Grid.ColumnSpan="2" Orientation="Horizontal" >
<Label Text="Ref.: " FontSize="24" FontAttributes="Bold" TextColor="#000000" />
<Label Text="{Binding Reference}" FontSize="24" TextColor="#000000" />
</StackLayout>
...
<Entry Grid.Row="3" Grid.Column="1" Text="{Binding NumElements}"
Keyboard="Numeric" Placeholder="" FontSize="24"
HorizontalTextAlignment="Center" Focused="OnItemFocus"
Unfocused="OnItemUnfocus" />
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I want to achieve two things with this Entry control that I'm not able achieve:
First, when I add a new item, I would like that this new item has his Entry the focus, ready to start typing.
Second, when the user ends to write a value into the Entry, I would like to change the value of the behind. I would like know which Entry of ListView has modified. I tried to use the Unfocused event, but in the params of the method that launches only has a sender param that returns the Entry object, no reference about the model that has binded.
public void OnItemUnfocus(object sender, EventArgs e)
{
Entry entry = (Entry)sender;
//Here I would like to know the model object that's binded
//with this Entry / CellView item
}
How I can achieve these two points?
I'd like to suggest you to use behaviors:
public class FocusBehavior : Behavior<Entry>
{
private Entry _entry;
public static readonly BindableProperty IsFocusedProperty =
BindableProperty.Create("IsFocused",
typeof(bool),
typeof(FocusBehavior),
default(bool),
propertyChanged: OnIsFocusedChanged);
public int IsFocused
{
get { return (int)GetValue(IsFocusedProperty); }
set { SetValue(IsFocusedProperty, value); }
}
protected override void OnAttachedTo(Entry bindable)
{
base.OnAttachedTo(bindable);
_entry = bindable;
}
private static void OnIsFocusedChanged(BindableObject bindable, object oldValue, object newValue)
{
var behavior = bindable as FocusBehavior;
var isFocused = (bool)newValue;
if (isFocused)
{
behavior._entry.Focus();
}
}
}
<ListView x:Name="TasksListView"
ItemsSource={Binding Tasks}
RowHeight="200">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="ViewCell">
<Grid x:Name="RootGrid"
Padding="10,10,10,0"
BindingContext="{Binding}">
<Entry>
<Entry.Behaviors>
<helpers:FocusBehavior IsFocused="{Binding BindingContext.IsFocused, Source={x:Reference RootGrid}}"/>
</Entry.Behaviors>
</Entry>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And my model:
public class TaskModel : INotifyPropertyChanged
{
private bool _isFocused;
public bool IsFocused
{
get { return _isFocused; }
set
{
_isFocused = value;
RaisePropertyChanged();
}
}
And in ViewModel, after adding new item, set it's IsFocused property to true.
The same thing with behavior you could use for TextChanged for Entry.
For my first question, I have found a way to solve it. I don't know if it is the best solution.
I have extended ListView to CustomListView, and I have added and a dictionary of cells:
private Dictionary<string, Cell> dicCells;
Also, I have overridden SetupContent and UnhookContent methods.
SetupContent fires when new cell has been added and one of his params gives me the new cell that I save into the dictionary. (MarkReference is my key value)
//When a new cell item has added, we save it into a dictionary
protected override void SetupContent(Cell content, int index)
{
base.SetupContent(content, index);
ViewCell vc = (ViewCell)content;
if (vc != null)
{
BasketProduct bp = (BasketProduct)vc.BindingContext;
if (bp != null)
{
this.dicCells.Add(bp.MarkReference, content);
}
}
}
UnhookContent fires when a cell has been removed. I remove the item that exists into my dictionary.
//When a new cell item has removed, we remove from the dictionary
protected override void UnhookContent(Cell content)
{
base.UnhookContent(content);
ViewCell vc = (ViewCell)content;
if (vc != null)
{
BasketProduct bp = (BasketProduct)vc.BindingContext;
if (bp != null)
{
this.dicCells.Remove(bp.MarkReference);
}
}
}
Then, I have created a function that retrieves a Entry (CustomEntry in my case) that contains the object (BasketProduct in my case).
//Retrieves a CustomEntry control that are into the collection and represents the BasketProduct that we have passed
public CustomEntry GetEntry(BasketProduct bp)
{
CustomEntry ce = null;
if (bp != null && this.dicCells.ContainsKey(bp.MarkReference))
{
ViewCell vc = (ViewCell)this.dicCells[bp.MarkReference];
if (vc != null)
{
ce = (CustomEntry)((Grid)((StackLayout)vc.View).Children[0]).Children[4];
}
}
return ce;
}
When I want to give the focus on a certain Entry, I call this method:
//Put the focus on the CustomEntry control that represents de BasketProduct that they have passed
public void SetSelected(BasketProduct bp, bool withDelay)
{
CustomEntry entry = null;
entry = GetEntry(bp);
if (entry != null)
{
if (withDelay)
{
FocusDelay(entry);
} else
{
entry.Focus();
}
}
}
If I call the SetSelected() method from ItemTapped method, works fine, but if I call the SetSelected() method after adding a item in then collection, the Entry doesn't get the focus. In this case, I have done a trick.
private async void FocusDelay(CustomEntry entry)
{
await Task.Delay(500);
entry.Focus();
}
About second question, as #markusian suggested, I have extended the Entry (CustomEntry) control and in the Unfocused event I have done this:
private void CustomEntry_Unfocused(object sender, FocusEventArgs e)
{
try
{
//If the user leaves the field empty, we set the last value
BasketProduct bp = (BasketProduct)BindingContext;
if (this.Text.Trim().Equals(string.Empty))
{
this.Text = bp.NumElements.ToString();
}
}
catch (FormatException ex) { }
}
I have structured the classes like this for my Universal app. Currently working in the WP8.1 part.
The following classes are put in the shared code. (Hoping to use it in Win8.1)
FolderItemViewer.xaml(UserControl) It is the DataTemplate for a ListView in the MainPage.xaml
FolderCollection class, which is the collection that is bound to the Listview in the Mainpage.xaml of the WP
Now the problem is, I have wired the manipulation events to the datatemplate grids in the FolderItemViewer.xaml to capture the right and left swipe and it is working. Now based on this, I need to update that CollectionItem in FolderCollection class and hence the ListView in Mainpage.xaml.
How do I capture that listview item or the collectionitem bound since the manipulation events lie in the FolderItemViewer class?
Can I get the listview item? Or a call back function to the listlivew item template changed? or somethin like that?
EDIT
Sorry to put in so much code. But really appreciate somebody's help in getting this right.
This is the FolderItemViewer.xaml
<UserControl
x:Class="JusWrite2.FolderItemViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:JusWrite2"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid x:Name="MainGrid">
<Grid Height="60" Width="380" Margin="0,0,0,1">
<Grid x:Name="ItemGrid" HorizontalAlignment="Left" VerticalAlignment="Center" Width="380" Height="60" Background="Transparent" Canvas.ZIndex="2"
ManipulationMode="TranslateX,System" ManipulationStarted="On_ChannelItem_ManipulationStarted" ManipulationDelta="On_ChannelItem_ManipulationDelta" ManipulationCompleted="OnChannelItemManipulationCompleted">
<TextBlock x:Name="titleTextBlock" Margin="20,0,0,0" Canvas.ZIndex="2" VerticalAlignment="Center" TextAlignment="Left" FontSize="25" >
</TextBlock>
</Grid>
<Grid x:Name="DelGrid" Opacity="0.0" HorizontalAlignment="Right" VerticalAlignment="Center" Height="60" Background="Red" Canvas.ZIndex="-1" Tapped="On_ChannelDelete_Tap" Width="380">
<Button Content="X" FontSize="25" Canvas.ZIndex="-1" VerticalAlignment="Center" HorizontalAlignment="Center" Width="380" BorderThickness="0" />
</Grid>
</Grid>
</Grid>
</UserControl>
code behind
private void OnChannelItemManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
{
Grid ChannelGrid = (Grid)sender;
Grid mGrid = (Grid)(ChannelGrid.Parent);
Grid DeleteGrid = (Grid)((Grid)(ChannelGrid.Parent)).Children[1];
FolderCollection swipedItem = ChannelGrid.DataContext as FolderCollection;// grid has null value for datacontext
double dist = e.Cumulative.Translation.X;
if (dist < -100)
{
// Swipe left
}
else
{
// Swipe right
}
}
FolderCollection.xaml has two classes in it. FolderItem and FolderCollection
public class FolderItem : INotifyPropertyChanged
{
// variables
public FolderItem()
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public int CompletionStatus
{
//code
}
public int Priority
{
//code
}
public string FolderText
{
//code
}
public int PenColor
{
//code
}
public string UUID
{
//code
}
public string CreateUUID()
{
//code
}
}
public class FolderCollection : IEnumerable<Object>
{
private ObservableCollection<FolderItem> folderCollection = new ObservableCollection<FolderItem>();
private static readonly FolderCollection instance = new FolderCollection();
public static FolderCollection Instance
{
get
{
return instance;
}
}
public IEnumerator<Object> GetEnumerator()
{
return folderCollection.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(FolderItem fItem)
{
folderCollection.Add(fItem);
}
public ObservableCollection<FolderItem> FolderCollectionInstance
{
get
{
return folderCollection;
}
}
}
And this is the MainPage.xaml where I have data bound.
// Resources
<DataTemplate x:Key="StoreFrontTileTemplate">
<local:FolderItemViewer />
</DataTemplate>
<ListView x:Name="FolderListView" ItemsSource="{Binding}"
SelectionMode="None"
ItemTemplate="{StaticResource StoreFrontTileTemplate}"
ContainerContentChanging="ItemListView_ContainerContentChanging">
</ListView>
code behind
//Constructor
FolderListView.DataContext = fc.FolderCollectionInstance;
FolderListView.ItemsSource = fc.FolderCollectionInstance;
private void ItemListView_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
FolderItemViewer iv = args.ItemContainer.ContentTemplateRoot as FolderItemViewer;
if (args.InRecycleQueue == true)
{
iv.ClearData();
}
else if (args.Phase == 0)
{
iv.ShowPlaceholder(args.Item as FolderItem);
// Register for async callback to visualize Title asynchronously
args.RegisterUpdateCallback(ContainerContentChangingDelegate);
}
else if (args.Phase == 1)
{
iv.ShowTitle();
args.RegisterUpdateCallback(ContainerContentChangingDelegate);
}
else if (args.Phase == 2)
{
//iv.ShowCategory();
//iv.ShowImage();
}
// For imporved performance, set Handled to true since app is visualizing the data item
args.Handled = true;
}
private TypedEventHandler<ListViewBase, ContainerContentChangingEventArgs> ContainerContentChangingDelegate
{
get
{
if (_delegate == null)
{
_delegate = new TypedEventHandler<ListViewBase, ContainerContentChangingEventArgs>(ItemListView_ContainerContentChanging);
}
return _delegate;
}
}
private TypedEventHandler<ListViewBase, ContainerContentChangingEventArgs> _delegate;
The list item should be available as the data context of the grid: ChannelGrid.DataContext.
I am working in WPF -- There is button with click event handler in my application. As i click on button it's event handler generates a new row in grid named as grids. In this new Row i want to add another grid programmatically to add Label, Button and TextBox in this grid in row.
As i executed my code it only generates a texboxes! where labels and button shown once! Here code and image is : Please feel free to ask if my query is not clear to you!
int r =0;
private void button2_Click(object sender, RoutedEventArgs e)
{
TextEdit text1; Button button1; Grid grid1;
grids.RowDefinitions.Add(new RowDefinition());
text1 = new TextEdit();
text1.SetValue(Grid.ColumnProperty, 1);
text1.SetValue(Grid.RowProperty, r);
button1 = new Button();
button1.Content = "Left + " + r;
button1.Click += new RoutedEventHandler(button1_Click);
button1.SetValue(Grid.ColumnProperty, 1);
button1.SetValue(Grid.RowProperty, r);
grid1 = new Grid();
grid1.SetValue(Grid.ColumnProperty, 1);
grids.RowDefinitions.Add(new RowDefinition());
grid1.SetValue(Grid.RowProperty, r);
grids.Children.Add(button1);
grids.Children.Add(text1);
r = r + 1;
}
EDIT
int r =0;
private void button2_Click(object sender, RoutedEventArgs e)
{
db obj = new db();
var query = from p in obj.TableA select p ;
foreach(var a in query.ToList())
{
TextEdit text1; Button button1; Grid grid1;
grids.RowDefinitions.Add(new RowDefinition());
text1 = new TextEdit();
text1.SetValue(Grid.ColumnProperty, 1);
text1.SetValue(Grid.RowProperty, r);
button1 = new Button();
button1.Content = a.name;
button1.Click += new RoutedEventHandler(button1_Click);
button1.SetValue(Grid.ColumnProperty, 1);
button1.SetValue(Grid.RowProperty, r);
grid1 = new Grid();
grid1.SetValue(Grid.ColumnProperty, 1);
grids.RowDefinitions.Add(new RowDefinition());
grid1.SetValue(Grid.RowProperty, r);
grids.Children.Add(button1);
grids.Children.Add(text1);
r = r + 1;}
}
Ok. Delete all your code and start all over.
If you're working with WPF, you really need to have The WPF Mentality
As a general rule, you almost never create or manipulate UI elements in procedural code in WPF.
That's what XAML is for.
This the right way to do what you're asking in WPF (in a full working example):
<Window x:Class="MiscSamples.ItemsControlSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
Title="ItemsControlSample" Height="300" Width="300">
<DockPanel>
<Button Content="Add New Row" Command="{Binding AddNewRowCommand}"
DockPanel.Dock="Bottom"/>
<ItemsControl ItemsSource="{Binding Data}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" Background="Gainsboro" BorderThickness="1" Margin="2">
<!-- This is the Inner Grid for each element, which is represented in Brown color in your picture -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width=".2*"/>
<ColumnDefinition Width=".2*"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Label1Text}"
Margin="2"/>
<Button Content="Button1"
Command="{Binding DataContext.Command1, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}"
Grid.Column="1" Margin="2"/>
<Button Content="Button2"
Command="{Binding DataContext.Command2, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}"
Grid.Column="2" Margin="2"/>
<dxe:TextEdit Text="{Binding Text}"
Grid.Row="1" Grid.ColumnSpan="3"
Margin="2"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<ScrollViewer CanContentScroll="True">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DockPanel>
</Window>
Code Behind:
public partial class ItemsControlSample : Window
{
public ItemsControlSample()
{
InitializeComponent();
DataContext = new ItemsControlSampleViewModel();
}
}
ViewModel:
public class ItemsControlSampleViewModel
{
public ObservableCollection<ItemsControlSampleData> Data { get; set; }
public Command AddNewRowCommand { get; set; }
public Command<ItemsControlSampleData> Command1 { get; set; }
public Command<ItemsControlSampleData> Command2 { get; set; }
public ItemsControlSampleViewModel()
{
var sampledata = Enumerable.Range(0, 10)
.Select(x => new ItemsControlSampleData()
{
Label1Text = "Label1 " + x.ToString(),
Text = "Text" + x.ToString()
});
Data = new ObservableCollection<ItemsControlSampleData>(sampledata);
AddNewRowCommand = new Command(AddNewRow);
Command1 = new Command<ItemsControlSampleData>(ExecuteCommand1);
Command2 = new Command<ItemsControlSampleData>(ExecuteCommand2);
}
private void AddNewRow()
{
Data.Add(new ItemsControlSampleData() {Label1Text = "Label 1 - New Row", Text = "New Row Text"});
}
private void ExecuteCommand1(ItemsControlSampleData data)
{
MessageBox.Show("Command1 - " + data.Label1Text);
}
private void ExecuteCommand2(ItemsControlSampleData data)
{
MessageBox.Show("Command2 - " + data.Label1Text);
}
}
Data Item:
public class ItemsControlSampleData
{
public string Label1Text { get; set; }
public string Text { get; set; }
}
Helper classes:
public class Command : ICommand
{
public Action Action { get; set; }
public string DisplayName { get; set; }
public void Execute(object parameter)
{
if (Action != null)
Action();
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged;
public Command(Action action)
{
Action = action;
}
}
public class Command<T>: ICommand
{
public Action<T> Action { get; set; }
public void Execute(object parameter)
{
if (Action != null && parameter is T)
Action((T)parameter);
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged;
public Command(Action<T> action)
{
Action = action;
}
}
Result:
Notice how I'm not dealing with UI in procedural code, but instead I'm using DataBinding and simple, simple properties. That's how you program in WPF. That's what the WPF mentality is about.
I'm using an ItemsControl and a DataTemplate defined in XAML to let WPF create the UI for each of my data items.
Also notice how my code does nothing except expose the data and define reusable Commands that serve as abstractions to the user actions such as Button clicks. This way you can concentrate in coding your business logic instead of struggling with how to make the UI work.
The buttons inside each item are bound to the Commands using a RelativeSource Binding to navigate upwards in the Visual Tree and find the DataContext of the ItemsControl, where the Commands are actually defined.
When you need to add a new item, you just add a new item to the ObservableCollection that contains your data and WPF automatically creates the new UI elements bound to that.
Though this might seem like "too much code", most of the code I posted here is highly reusable and can be implemented in a Generic ViewModel<T> that is then reusable for any type of data items. Command and Command<T> are also write-once reusable classes that can be found in any MVVM framework such as Prism, MVVM Light or Caliburn.Micro.
This approach is really much preferred in WPF, because it enables a great amount of scalability and independence between the UI and the business logic, and it also enables testability of the ViewModel.
I suggest you read all the materials linked in the post, most importantly Rachel's WPF Mentality and related blog posts. Let me know if you need further help.
WPF Rocks. Just copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.
It's actually much easier in behind code then in xaml code..
My Xaml code:
<Window x:Class="WpfAddGridWithStackPanel.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>
<Grid x:Name="Grid_Grid" Margin="0,0,0,32">
<Grid>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid x:Name="Grid_Grid" Margin="0,0,0,32"/>
</ScrollViewer>
<Button x:Name="btn_Add" Height="32" DockPanel.Dock="Bottom" VerticalAlignment="Bottom" Content="Add New Row" Click="btn_Add_Click" Width="150" HorizontalAlignment="Left" UseLayoutRounding="True" />
<Button x:Name="btn_Remove" Height="32" DockPanel.Dock="Bottom" VerticalAlignment="Bottom" Content="Remove last Row" Click="btn_Remove_Click" Width="150" HorizontalAlignment="Right" />
</Grid>
</Window>
And Code behind:
public partial class MainWindow : Window
{
int num = 0;
public MainWindow()
{
InitializeComponent();
}
void btn1_Click(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
void btn2_Click(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
private void btn_Remove_Click(object sender, RoutedEventArgs e)
{
try
{
Grid_Grid.RowDefinitions.RemoveAt(Grid_Grid.RowDefinitions.Count - 1);
Grid_Grid.Children.RemoveAt(Grid_Grid.Children.Count - 1);
num--;
}
catch { }
}
private void btn_Add_Click(object sender, RoutedEventArgs e)
{
StackPanel stack = new StackPanel();
DockPanel dock = new DockPanel();
Label lbl = new Label();
Button btn1 = new Button();
Button btn2 = new Button();
TextBox txt1 = new TextBox();
stack.Children.Add(dock);
stack.Children.Add(txt1);
dock.Children.Add(lbl);
dock.Children.Add(btn2);
dock.Children.Add(btn1);
#region StackPanel Properties
stack.Background = Brushes.LightGray;
#endregion
#region DockPanel Content Properties
lbl.Content = "Label " + (num + 1).ToString();
lbl.Height = 32;
lbl.Width = 100;
lbl.FontSize = 12;
lbl.SetValue(DockPanel.DockProperty, Dock.Left);
lbl.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
btn1.Content = "Butten 1";
btn1.Height = 32;
btn1.Width = 100;
btn1.FontSize = 12;
btn1.HorizontalAlignment = System.Windows.HorizontalAlignment.Right;
btn1.SetValue(DockPanel.DockProperty, Dock.Right);
btn1.Click += new RoutedEventHandler(btn1_Click);
btn2.Content = "Butten 2";
btn2.Height = 32;
btn2.Width = 100;
btn2.FontSize = 12;
btn2.HorizontalAlignment = System.Windows.HorizontalAlignment.Right;
btn2.SetValue(DockPanel.DockProperty, Dock.Right);
btn2.Click += new RoutedEventHandler(btn2_Click);
#endregion
#region TextBox Properties
txt1.Text = "Text " + (num + 1).ToString();
txt1.Height = 32;
txt1.Width = double.NaN;
txt1.FontSize = 12;
txt1.Padding = new Thickness(0, 7, 0, 7);
#endregion
Grid_Grid.RowDefinitions.Add(new RowDefinition());
Grid_Grid.RowDefinitions[num].Height = new GridLength(66, GridUnitType.Pixel);
Grid_Grid.Children.Add(stack);
stack.SetValue(Grid.RowProperty, num);
num++;
}
}
I have a DataTemplate defined as follows
<DataTemplate x:Key="PasswordViewerTemplate">
<StackPanel>
<TextBlock Text="{Binding PasswordChar, ElementName=this}"
Visibility="Visible" />
<TextBox Text="{Binding PasswordText}"
Visibility="Collapsed" />
</StackPanel>
</DataTemplate>
I want to be able to toggle visibilities of the TextBlock and the TextBox each time the user clicks on the StackPanel. I tried setting a MouseLeftButtonUp event handler on the StackPanel but this throws an exception
Object reference not set to an instance of an object
Is there another way to achieve this? Maybe in XAML itself using triggers?
Also, this might be relevant. The above template is one of two that is applied to a ListBox by a template selector. The ListBox itself is within a Grid and both templates are defined within the Grid.Resources section.
EDIT 1
I tried setting the event as follows
<StackPanel MouseLeftButtonUp="OnPasswordViewerMouseLeftButtonUp">
...
</StackPanel>
private void OnPasswordViewerMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var sp = sender as StackPanel;
if( ( sp == null ) || ( sp.Children.Count != 2 ) ) {
return;
}
var passwordText = sp.Children[0] as TextBlock;
var plainText = sp.Children[1] as TextBox;
if( ( passwordText == null ) || ( plainText == null ) ) {
return;
}
passwordText.Visibility = ( passwordText.Visibility == Visibility.Visible ) ?
Visibility.Collapsed : Visibility.Visible;
plainText.Visibility = ( plainText.Visibility == Visibility.Visible ) ?
Visibility.Collapsed : Visibility.Visible;
}
One of the solutions is to bind visibility of the TextBox and TextBlock to properties of the class which is used as DataContext for the StackPanel. Here is a sample implementation:
Xaml code:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="PasswordViewerTemplate">
<StackPanel PreviewMouseUp="StackPanel_PreviewMouseUp">
<TextBlock Text="{Binding Path=PasswordChar}"
Visibility="{Binding Path=TextBlockVisibility}" />
<TextBox Text="{Binding Path=PasswordText}"
Visibility="{Binding Path=TextBoxVisibility}" />
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ListBox x:Name="lbox" ItemTemplate="{StaticResource ResourceKey=PasswordViewerTemplate}" ItemsSource="{Binding}"/>
</Grid>
And C# code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObservableCollection<Some> items = new ObservableCollection<Some>();
for (int i = 0; i < 10; i++)
{
items.Add(new Some(string.Format("passwordChar {0}", i + 1), string.Format("passwordText {0}", i + 1), Visibility.Visible, Visibility.Collapsed));
}
this.lbox.ItemsSource = items;
}
private void StackPanel_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
Some some = (sender as StackPanel).DataContext as Some;
some.TextBlockVisibility = ToggleVisibility(some.TextBlockVisibility);
some.TextBoxVisibility = ToggleVisibility(some.TextBoxVisibility);
}
private Visibility ToggleVisibility(Visibility visibility)
{
return visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
}
}
public class Some:INotifyPropertyChanged
{
private string _passwordChar;
private string _passwordText;
private Visibility _textBlockVisibility, _textBoxVisibility;
public string PasswordChar { get { return this._passwordChar; } set { this._passwordChar = value; } }
public string PasswordText { get { return this._passwordText; } set { this._passwordText = value; } }
public Visibility TextBlockVisibility
{
get { return this._textBlockVisibility; }
set
{
this._textBlockVisibility = value;
RaisePropertyChanged("TextBlockVisibility");
}
}
public Visibility TextBoxVisibility
{
get { return this._textBoxVisibility; }
set
{
this._textBoxVisibility = value;
RaisePropertyChanged("TextBoxVisibility");
}
}
public Some(string passwordChar, string passwordText, Visibility textBlockVisibility, Visibility textBoxVisibility)
{
this._passwordChar = passwordChar;
this._passwordText = passwordText;
this._textBlockVisibility = textBlockVisibility;
this._textBoxVisibility = textBoxVisibility;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
Why dont you bind the item's visibility in your view model?
an Example.
<Textblock Test="{Binding passwordText,ElementName=This}" Visibility="{Binding passwordTextVisibility}"/>
in your ViewModel say
public Visibility passwordTextVisibility
{
getters and setters here
}
and on your mouse event, you would need some sort of routed event inside the stack panel. an example:
inside the stack panel you would need mouse. whatever you need. read a little about routed events
Example. if PreviewMouseLeftButtonUp does not work.
<StackPanel Mouse.MouseUp="MouseButtonUpEventHandler"/>
in the view model
public void MouseButtonUpEventHandler (RoutedEvent e)
{
//logic here to check if it's left mouse if it is then set visibility
}
}