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.
Related
<Window x:Class="WpfApp12.MainWindow"
xmlns=...usual namespaces...
Loaded="Window_Loaded"
>
<Window.Resources>
<DataTemplate x:Key="myHeaderTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{TemplateBinding Content}"/>
<Button Margin="5,0,0,0" x:Name="MyButton">Press me</Button>
</StackPanel>
</DataTemplate>
</Window.Resources>
<!-- Just point the datacontext to the code behind -->
<Window.DataContext>
<Binding RelativeSource="{RelativeSource Mode=Self}"/>
</Window.DataContext>
<DataGrid Name="DG" ItemsSource="{Binding People}"/>
</Window>
This, togheter with the code behind below gives just what I want: a DataGrid with a column whose header has been dynamically assigned a DataTemplate with a Button "Press me":
The code behind:
public partial class MainWindow : Window
{
public class Person
{
public string Name { get; set; }
public string Surname { get; set; }
}
public MainWindow()
{
People.Add(new Person() { Name = "Isaac", Surname = "Newton" });
People.Add(new Person() { Name = "Galileo", Surname = "Galilei" });
InitializeComponent();
}
public ObservableCollection<Person> People { get; } = new ObservableCollection<Person>();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DG.Columns[0].HeaderTemplate = (DataTemplate)FindResource("myHeaderTemplate");
//how to access the button in the template in order to assign the click event?
}
private void MyButton_Click(object sender, RoutedEventArgs e)
{
//DO SOMETHING
}
}
}
Now i want to dynamically wire MyButton_Click event to the button in the template.
This kind of problems seem to have a lot of coverage, this one being one of the best:
WPF How to access control from DataTemplate
There there is something like:
ComboBox myCombo = _contentPresenter.ContentTemplate.FindName("myCombo", _contentPresenter) as ComboBox;
I'm not very familiar with the templating, and I cannot find the starting point, the "content presenter" on which to call the FindName.
You could use a recursive helper method that finds the Button in the visual tree:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DG.Columns[0].HeaderTemplate = (DataTemplate)FindResource("myHeaderTemplate");
DG.Dispatcher.BeginInvoke(new Action(() =>
{
DataGridColumnHeadersPresenter presenter = FindVisualChild<DataGridColumnHeadersPresenter>(DG);
DataGridCellsPanel dataGridCellsPanel = FindVisualChild<DataGridCellsPanel>(presenter);
DataGridColumnHeader header = dataGridCellsPanel.Children[0] as DataGridColumnHeader;
Button button = FindVisualChild<Button>(header);
if (button != null)
button.Click += MyButton_Click;
}));
}
private void MyButton_Click(object sender, RoutedEventArgs e)
{
//DO SOMETHING
}
private static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is T)
return (T)child;
else
{
T childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
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;
}
}
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 created my own button that has Icon on the side and text on the other but the problem is the image is not displaying. did i miss something here? any help would be appreciated. TIA
This is the XAML of the control.
<UserControl x:Name="QButtonControl"
x:Class="CommonLayout.QButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CommonLayout"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="36"
d:DesignWidth="145" MinWidth="145" MinHeight="36" Loaded="QButtonControl_Loaded">
<Grid PointerEntered="Grid_PointerEntered_1" PointerExited="Grid_PointerExited_1" MinWidth="145" MinHeight="36" Background="#FFDCD1D1">
<TextBlock x:Name="btnLabel" Height="20" Margin="36,8,4,8" TextWrapping="Wrap" Text="Text Here" VerticalAlignment="Center" FontSize="18.667" Width="105"/>
<Image x:Name="img" HorizontalAlignment="Left" Height="27" Margin="1,4,0,0" VerticalAlignment="Top" Width="29"/>
</Grid>
</UserControl>
This is code behind the control.
public sealed partial class QButton : UserControl
{
private ImageSource iconDefault;
private Brush hoverBrush = new SolidColorBrush(Color.FromArgb(255, 228, 228, 228));
public string Text
{
get
{
return btnLabel.Text;
}
set
{
btnLabel.Text = value;
}
}
public ImageSource Icon
{
get
{
return iconDefault;
}
set
{
iconDefault = value;
img.Source = value;
}
}
public Brush HoverBrush
{
get
{
return hoverBrush;
}
set
{
hoverBrush = value;
}
}
public QButton()
{
this.InitializeComponent();
}
private void Grid_PointerEntered_1(object sender, PointerRoutedEventArgs e)
{
btnLabel.Foreground = HoverBrush;
}
private void Grid_PointerExited_1(object sender, PointerRoutedEventArgs e)
{
btnLabel.Foreground = Foreground;
}
private void QButtonControl_Loaded(object sender, RoutedEventArgs e)
{
img.Source = iconDefault;
}
}
First of all, do not use a hard coded file path to the image.
Windows Store apps run in a sandbox, so you will not be able to get to any arbitrary file location when you deploy your app.
Second, you can't use backslashes in Image URI. The backslashes are the technical reason you are setting the error you are getting. But just changing to forward slashes in not the answer.
Access Image in XAML
If you add an image to your projects /Assets folder, you can use XAML like this to show it in QButton.
<local:QButton x:Name='qButton1'
Icon='/assets/jellyfish.jpg' />
In Code
To change the Icon in code.
public MainPage()
{
this.InitializeComponent();
this.Loaded += MainPage_Loaded;
}
private async void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/shrimp.jpg"));
var fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
var image = new BitmapImage();
image.SetSource(fileStream);
qButton1.Icon = image;
}
If you want the user to choose the Image from their computer at runtime, look at the File Pickers.
MSDN file pickers
Are you forgetting to set the Icon property?
This worked for me
<local:QButton Icon="C:\Users\Public\Pictures\Sample Pictures\Penguins.jpg" />
This worked as well
public QButton()
{
this.InitializeComponent();
Uri imageUri = new Uri(#"C:\Users\Public\Pictures\Sample Pictures\Penguins.jpg");
BitmapImage image = new BitmapImage(imageUri);
this.Icon = image;
}