Creating bindings in code (WinRT) - c#

I have the following classes
ImageViewModel: INotifyPropertyChanged
{ ...
String Url;
}
AdViewModel: INotifyPropertyChanged
{ ...
ImageViewModel Image
}
The AdViewModel perodicaly changes the Image property (animated Ad).
When I have the following XAML:
<Grid>
<Image Source="{Binding Image.Url}"
Width="{Binding Image.Width}"
Height="{Binding Image.Height}" />
And set the Grids DataContext to an instance of AdViewModel everything works as expected. But I need to create the XAML in C# code to use it elsewhere. Creating a Grid and appending an Image as its child is easy, but how to a create the bindings?

try something along the lines of
AdViewModel vm = new AdViewModel;
Binding binding = new Binding
{
Path = new PropertyPath("Width"),
Source = vm.Image
};
nameOfGridInXaml.SetBinding(Image.WidthProperty, binding);

I found an easier way. I created the XAML as a UserControl, saved it in a file (Templates\SkyScrapper.xaml). Then instead of creating the controls in C# a just load the XAML File
var _Path = #"Templates\SkyScrapper.xaml";
var _Folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
var _File = await _Folder.GetFileAsync(_Path);
var _ReadThis = await Windows.Storage.FileIO.ReadTextAsync(_File);
DependencyObject rootObject = XamlReader.Load(_ReadThis) as DependencyObject;
var uc = (UserControl)rootObject;
and set its DataContext
uc.DataContext = ad;
There is now no need to create the bindings in C#, they are defined in the XAML file.

Related

how to display an image on xamarin forms using binding tag from image

Using xamarin forms trying to display an image using binding...followed a few online Q's already including the answer on this one https://stackoverflow.com/questions/30850510/how-to-correctly-use-the-image-source-property-with-xamarin-forms just cant seem to get the image to display...yes I knoW I can load the image by using <Image Source="Bud.jpeg"></Image> which works fine....but I would like to display it using binding....
eg..
xaml
<Image Source="{Binding imageTest}"></Image>
code
var imageTest = new Image { Aspect = Aspect.AspectFit };
imageTest.Source = ImageSource.FromFile("Guinness.jpg");
anyone any idea why? thanks
You can only bind to public properties
<Image Source="{Binding imageTest}" />
then declare a public property in your code-behind
public string imageTest { get; set; }
and then set the property value and BindingContext
imageTest = "Guinness.jpg";
this.BindingContext = this;

WPF Image won't update programmatically

I have an application where I want it to load an image when a command is invoked. But the problem is that nothing loads and nothing breaks either. I just dont see my image. I also made sure that I was setting the data context to my view model.
XAML:
<Image Grid.Column="3" Source="{Binding Path=LoadingImage, Mode=TwoWay}" Width="35" Height="35"/>
ViewModel:
private Image _loadingImage = new Image();
public Image LoadingImage
{
get => _loadingImage;
set
{
_loadingImage = value;
RaisePropertyChanged(nameof(LoadingImage));
}
}
//Method called by the command... i debugged it and it gets here just fine
private void GetDirectories()
{
FolderBrowserDialog folderBrowseDialog = new FolderBrowserDialog();
DialogResult result = folderBrowseDialog.ShowDialog();
if (result == DialogResult.OK)
{
//This is how I am getting the image file
LoadingImage.Source = new BitmapImage(new Uri("pack://application:,,,/FOONamespace;component/Resources/spinner_small.png"));
//More code below
}
}
Some other settings, my .png file has the following properties:
Build Action: Resource
Copy to Output Directory: Copy if newer
This is head scratcher for me. What am I doing wrong? Many thanks.
You can't use an Image element as the value of the Source property of another Image element.
Change the property type to ImageSource:
private ImageSource _loadingImage;
public ImageSource LoadingImage
{
get => _loadingImage;
set
{
_loadingImage = value;
RaisePropertyChanged(nameof(LoadingImage));
}
}
and assign the property like this:
LoadingImage = new BitmapImage(
new Uri("pack://application:,,,/FOONamespace;component/Resources/spinner_small.png"));
Besides that, setting the Binding's Mode to TwoWay is pointless
<Image Source="{Binding LoadingImage}" />
and copying to the output directory is also unnecessary, because the Build Action Resource makes the image file an assembly resource that is compiled into the assembly.

WPF Binding Image Source

maybe stupid question, but I don't know anymore...
I have ViewModel class like this:
public class MainWindowsViewModel : INotifyPropertyChanged
{
private ImageSource _img;
public ImageSource StatusImage
{
get { return _img; }
set
{
_img = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName]String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Binding in XAML looks like this:
<Window.DataContext>
<VM:MainWindowsViewModel />
</Window.DataContext>
<Image x:Name="gui_image_status" HorizontalAlignment="Left" Height="26" Margin="144,10,0,0" VerticalAlignment="Top" Width="29" Source="{Binding Path=StatusImage}" />
And I set content of ImageSource like this:
MainWindowsViewModel _view = new MainWindowsViewModel();
var yourImage = new BitmapImage(new Uri(String.Format("Sources/{0}.png", "red"), UriKind.Relative));
_view.StatusImage = yourImage;
But it does not work. I think that problem is in that NotifyPropertyChanged, because I tried place brake point in the set and get. Get triggered few times at the start, after then set triggered as well with correct ImageSource, but after then get did not triggered anymore. Like no setting ever happened.
It's really simply binding that I have done many times similarly...I don't know why it doesn't work this time.
You are creating two instances of your MainWindowsViewModel class, one in XAML by
<Window.DataContext>
<VM:MainWindowsViewModel />
</Window.DataContext>
and one in code behind by
MainWindowsViewModel _view = new MainWindowsViewModel();
So your code behind sets the property on a different view model instance than the one the view is bound to.
Change your code behind to this:
var viewModel = (MainWindowsViewModel)DataContext;
viewModel.StatusImage = new BitmapImage(...);
I didn't find any problems in your code, but you can try to check few things.
Check that your Image added to the project and set build action of images to Content (copy if newer).
Before updating ImageSource call Freeze method to prevent error: "Must create DependencySource on same Thread as the DependencyObject"
var yourImage = new BitmapImage(new Uri(String.Format("Sources/{0}.png", "red"), UriKind.Relative));
yourImage.Freeze();
_view.StatusImage = yourImage;
Also, there is an easier way to bind image in WPF. You can use string as a source and set a resource path to the binded property:
public string StatusImage
{
get { return "/AssemblyName;component/Sources/red.png"; }
}

Allowing named elements in a multi-content custom control

Requirement
Let's start with what I am trying to achieve. I want to have a grid with 2 columns and a grid splitter (there is a little more to it that that, but let's keep it simple). I want to be able to use this grid in a lot of different places, so instead of creating it each time I want to make a custom control that contain two ContentPresenters.
The end goal is effectively to be able to write XAML like this:
<MyControls:MyGrid>
<MyControls:MyGrid.Left>
<Label x:Name="MyLabel">Something unimportant</Label>
</MyControls:MyGrid.Left>
<MyControls:MyGrid.Right>
<Label>Whatever</Label>
</MyControls:MyGrid.Right>
</MyControls:MyGrid>
IMPORTANT: Notice that I want to apply a Name to my Label element.
Attempt 1
I did a lot of searching for solutions, and the best way I found was to create a UserControl along with a XAML file that defined my grid. This XAML file contained the 2 ContentPresenter elements, and with the magic of binding I was able to get something working which was great. However, the problem with that approach is not being able to Name the nested controls, which results in the following build error:
Cannot set Name attribute value 'MyName' on element 'MyGrid'. 'MyGrid'
is under the scope of element 'MyControls', which already had a name
registered when it was defined in another scope.
With that error in hand, I went back to Dr. Google...
Attempt 2 (current)
After a lot more searching I found some information here on SO that suggested the problem was due to having an associated XAML file with the MyGrid class, and the problem should be solvable by removing the XAML and creating all the controls via code in the OnInitialized method.
So I headed off down that path and got it all coded and compiling. The good news is that I can now add a Name to my nested Label control, the bad news is nothing renders! Not in design mode, and not when running the application. No errors are thrown either.
So, my question is: What am I missing? What am I doing wrong?
I am also open to suggestions for other ways to meet my requirements.
Current code
public class MyGrid : UserControl
{
public static readonly DependencyProperty LeftProperty = DependencyProperty.Register("Left", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
public object Left
{
get { return (object)GetValue(LeftProperty); }
set { SetValue(LeftProperty, value); }
}
public static readonly DependencyProperty RightProperty = DependencyProperty.Register("Right", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
public object Right
{
get { return (object)GetValue(RightProperty); }
set { SetValue(RightProperty, value); }
}
Grid MainGrid;
static MyGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyGrid), new FrameworkPropertyMetadata(typeof(MyGrid)));
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
//Create control elements
MainGrid = new Grid();
//add column definitions
ColumnDefinition leftColumn = new ColumnDefinition()
{
Name = "LeftColumn",
Width = new GridLength(300)
};
MainGrid.ColumnDefinitions.Add(leftColumn);
MainGrid.ColumnDefinitions.Add(new ColumnDefinition()
{
Width = GridLength.Auto
});
//add grids and splitter
Grid leftGrid = new Grid();
Grid.SetColumn(leftGrid, 0);
MainGrid.Children.Add(leftGrid);
GridSplitter splitter = new GridSplitter()
{
Name = "Splitter",
Width = 5,
BorderBrush = new SolidColorBrush(Color.FromArgb(255, 170, 170, 170)),
BorderThickness = new Thickness(1, 0, 1, 0)
};
MainGrid.Children.Add(splitter);
Grid rightGrid = new Grid();
Grid.SetColumn(rightGrid, 1);
MainGrid.Children.Add(rightGrid);
//add content presenters
ContentPresenter leftContent = new ContentPresenter();
leftContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Left") { Source = this });
leftGrid.Children.Add(leftContent);
ContentPresenter rightContent = new ContentPresenter();
rightContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Right") { Source = this });
rightGrid.Children.Add(rightContent);
//Set this content of this user control
this.Content = MainGrid;
}
}
After some discussion via comments, it quickly became clear that neither of my attempted solutions was the correct way to go about it. So I set out on a third adventure hoping this one would be the final solution... and it seems it is!
Disclaimer: I do not yet have enough experience with WPF to confidently say that my solution is the best and/or recommended way to do this, only that it definitely works.
First of all create a new custom control: "Add" > "New Item" > "Custom Control (WPF)". This will create a new class that inherits from Control.
In here we put our dependency properties for bind to out content presenters:
public class MyGrid : Control
{
public static readonly DependencyProperty LeftProperty = DependencyProperty.Register("Left", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
public object Left
{
get { return (object)GetValue(LeftProperty); }
set { SetValue(LeftProperty, value); }
}
public static readonly DependencyProperty RightProperty = DependencyProperty.Register("Right", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
public object Right
{
get { return (object)GetValue(RightProperty); }
set { SetValue(RightProperty, value); }
}
static MyGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyGrid), new FrameworkPropertyMetadata(typeof(MyGrid)));
}
}
When you add this class file in Visual Studio, it will automatically create a new "Generic.xaml" file in the project containing a Style for this control, along with a Control Template within that style - this is where we define our control elements...
<Style TargetType="{x:Type MyControls:MyGrid}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MyControls:MyGrid}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="500" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<ContentPresenter x:Name="LeftContent" />
</Grid>
<GridSplitter Width="5" BorderBrush="#FFAAAAAA" BorderThickness="1,0,1,0">
</GridSplitter>
<Grid Grid.Column="1">
<ContentPresenter x:Name="RightContent" />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The final step is to hook up the bindings for the 2 content presenters, so back to the class file.
Add the following override method to the MyGrid class:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
//Apply bindings and events
ContentPresenter leftContent = GetTemplateChild("LeftContent") as ContentPresenter;
leftContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Left") { Source = this });
ContentPresenter rightContent = GetTemplateChild("RightContent") as ContentPresenter;
rightContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Right") { Source = this });
}
And that's it! The control can now be used in other XAML code like so:
<MyControls:MyGrid>
<MyControls:MyGrid.Left>
<Label x:Name="MyLabel">Something unimportant</Label>
</MyControls:MyGrid.Left>
<MyControls:MyGrid.Right>
<Label>Whatever</Label>
</MyControls:MyGrid.Right>
</MyControls:MyGrid>
Thanks to #NovitchiS for your input, your suggestions were vital in getting this approach to work

Add Items to ComboBox Programmatically

I have been working with WPF all of 2 days, coming from ASP.NET so bear with me!
I am populating a ComboBox with xml filenames from a directory and adding a icon to each item. I have everything working just fine but I am wondering if there is a "better", more "efficient" way of doing this. As I stated, I am just getting started with WPF and I want to go about things the "right" way. My working code is below, can or should I be going about this a different way? Thanks in advance for any pointers!
<ComboBox Height="24" HorizontalAlignment="Left" Margin="153,138,0,0" Name="cmbFiles" VerticalAlignment="Top" Width="200" //>
private void FillSrFileCombo()
{
string[] dirFiles = Directory.GetFiles(#"D:\TestFiles", "*.xml");
foreach (string datei in dirFiles)
{
string fileName = System.IO.Path.GetFileName(datei);
System.Windows.Controls.StackPanel stkPanel = new StackPanel();
stkPanel.Orientation = Orientation.Horizontal;
cmbFiles.Items.Add(stkPanel);
System.Windows.Controls.Image cboIcon = new Image();
BitmapImage bitMap = new BitmapImage();
bitMap.BeginInit();
bitMap.UriSource = new Uri(#"tag.jpg", UriKind.Relative);
bitMap.EndInit();
cboIcon.Source = bitMap;
cboIcon.Height = 15;
stkPanel.Children.Add(cboIcon);
System.Windows.Controls.TextBlock cboText = new TextBlock();
cboText.Text = " - " + fileName;
stkPanel.Children.Add(cboText);
}
}
I have answered a similar question an hour ago see here :http://stackoverflow.com/questions/9637514/add-usercontrol-to-listbox-wpf.
I will recap the most important parts here based on your example
In the XAML you need to create a "DataTemplate", that is the XAML representation of your file object - in your case an image + file name. You can create this Datatemplate as a resource and assign it to your ComboBox or simply create it in the combobox if you don't plan to reuse it
<ComboBox ItemsSource="{Binding Files}">
<ComboBox.ItemTemplate>
<StackPanel>
<Image Source="{Binding FileImage}" Height="16" Width="16"/>
<TextBlock Margin="5" Text="{Binding FileName}" />
</StackPanel>
</ComboBox.ItemTemplate>
</ComboBox>
In your Codebehind, you need to create a structure that represets the data you want to present in your combobox - let's say a "FileInfo" class. The FileInfo class needs to expose the "FileImage" and "FileName" as properties so you can bind to them (as seen above).
Next, you need to create a collection of such objects in the code-behind of the xaml you put your ComboBox in. The collection needs to be an ObservableCollection.
So you would have smth like this:
public class FileInfo
{
public ImageSource FileImage { get; set; }
public string FileName { get; set; }
}
and then in the MainWindow.xaml.cs
public ObservableCollection Files { get; private set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Files = new ObservableCollection();
foreach (string datei in dirFiles)
{
var fName = System.IO.Path.GetFileName(datei);
BitmapImage bitMap = new BitmapImage();
bitMap.BeginInit();
bitMap.UriSource = new Uri(#"tag.jpg", UriKind.Relative);
bitMap.EndInit();
Files.Add(new FileInfo(){FileName=fName, FileImage = bitMap});
}
}
You will still need to read a lot about why this will work. I recomend reading about DataTemplates DataBinding, ObservableCollection and in the end, read about MVVM, a pattern that ties all this stuff nicely and allows you to harness all the WPF power and decouple yor logic from the UI.
One way that you should consider for WPF/Silverlight/WP7 apps is the MVVM design pattern.
In this instance you would have a view model containing the collection of items for your ComboBox, and you would use a binding expression to set the ItemsSource of the ComboBox. You would then template the ComboBox to display your item images.
Look into data binding and data templating, the only C# code you should need here is to get the files (even though you could also do that in XAML using something like an ObjectDataProvider)

Categories