I am quite new to WPF, and especially to DataBinding, and it is pretty hard for me to understand, how to code it without XAML. How can I bind parents visibility (parent is scrollViewer) to its child visibility (child is grid), without XAML? Here is what I am trying to do right now:
{
//Code that creates grid
//Code that creates scrollViewer
scrollViewer.Content = grid;
LayoutRoot.Children.Add(scrollViewer); //adding it to the main window
//Creating binding
Binding myBinding = new Binding("Vis");
BooleanToVisibilityConverter c = new BooleanToVisibilityConverter();
myBinding.Source = grid.Visibility;
myBinding.Converter = c;
myBinding.Mode = BindingMode.TwoWay;
myBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding(scrollViewer, VisibilityProperty, myBinding);
}
And here what is my "Vis" right now:
public Visibility Vis
{
get
{
return Visibility;
}
set
{
Visibility = value;
OnPropertyChanged(Visibility);
}
}
Ofcourse, right now problem is OnPropertyChanged, it gives me an error, and I need to put DependencyPropertyChangedEventArgs inside, but I do not know how to. I am not sure, am I doing this right way (maybe this can be simplified?), or maybe my method will not work at all, even after fixing this error. I need it to be without XAML, because controls are created dynamically.
Solution provided by PieterWitvoet worked for me. I had to set scrollViewer visibility to Visibility.Visible on its creation, but everything else works just fine.
Related
I have a weird bug and i need some rescue.
I have a grid with several column of multiple types in WPF.
One or several of these columns are DatePickers that I created through a FrameElementFactory :
FrameworkElementFactory dateFactory = new FrameworkElementFactory(typeof(DatePicker));
...
column = new DataGridTemplateColumn { CellTemplate = new DataTemplate
{ VisualTree = dateFactory } };
this._mainDatagrid.Columns.Add(column);
I have put a method to disable the DatePickers of my grid on a certain state of one of my variable:
private IEnumerable<DataGridRow> GetDataGridRows(DataGrid grid)
{
//return the Datagrid Rows
}
public void SetChangeLockState(bool isUnlocked)
{
IEnumerable<DataGridRow> _rows = this.GetDataGridRows(this._mainDatagrid);
foreach (DataGridColumn _column in this._mainDatagrid.Columns)
{
if (_column.GetType() != typeof(DataGridTemplateColumn)) continue;
foreach (DataGridRow _row in _rows)
{
FrameworkElement frameworkElement = _column.GetCellContent(_row);
if (frameworkElement != null) frameworkElement.IsEnabled = !isUnlocked;
}
}
}
The problem is that when I am playing with the elevator of my grid, the Datepicker keep enabling and disabling for no reason.
Example:
All my DatePicker are enabled, I am playing with my vertical scroll bar, no problem.
All my DatePickers are Disabled, I am playing with my vertical scroll bar.
1 Datepicker will suddenly appear enable :
DatePicker enabled 1
I am keeping playing with the scrollbar and another Datepicker will go enabled :
DatePicker enabled 2
Have you any idea of what could happen ?
Thanks for your help.
This will be because DataGrid.EnableRowVirtualization defaults to true. This enables UI virtualisation, which means that UI Elements that are scrolled out of view may be disposed or reused. Thus on occasions when scrolling an item back into view a new DatePicker will be created via your factory, and of course this new DatePicker will not have existed when SetChangeLockState was called and thus will not be disabled.
A quick fix would be to set DataGrid.EnableRowVirtualization to false, but this may not be very performant if you have lots of rows. A better solution would be to bind rather than set the IsEnabled property, e.g. to a property on your Window using RelativeSource.
Thanks to Ben, here is the code :
dateFactory.SetBinding(
//My IsEnabled property I wanted to change
IsEnabledProperty,
new Binding("IsLockedDatagrid")
{
//Datagridwidget is the datagrid I am using where I can found the IsLockedDatagrid boolean variable (in my xaml)
RelativeSource =
new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DataGridWidget), 1),
Mode = BindingMode.OneWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
I have a UserControl (Map) that contains a Canvas control. I am dynamically adding a control (Gate) to this canvas from the code behind.
I want the Gate objects DataContext to be the "Gate" property of the Map's DataContext. This is being done in the code behind.
Binding dataContextBinding = new Binding();
dataContextBinding.RelativeSource = new RelativeSource(RelativeSourceMode.Self);
dataContextBinding.Path = new PropertyPath("DataContext.SelectedLevelModule.Gate");
dataContextBinding.Mode = BindingMode.TwoWay;
dataContextBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding(gate, DataContextProperty, dataContextBinding);
After this block of code runs, the gate.DateContext is null...
Any ways this can be done? Drawing a blank..
Thanks
Harold
You are setting the property path to DataContext.SelectedLevelModule.Gate. You are then assigning the binding to the DataContextProperty. I think what is happening is that the path is now gate.DataContext.DataContext.SelectedLevelModule.Gate.
Try removing DataContext from your PropertyPath and see if that fixes it. You are already assigning it to the DataContext, you should not have to specify it in the Path.
var dataContextBinding = new Binding();
dataContextBinding.Path = new PropertyPath("SelectedLevelModule.Gate");
BindingOperations.SetBinding(gate, DataContextProperty, dataContextBinding);
Is there any way to access xaml class reference/name through its child ui control ?
the code is--->
private void AvailableItemListScrollViewer_Loaded(object sender, RoutedEventArgs e)
{
_listScrollViewer = sender as ScrollViewer;
Binding binding = new Binding();
binding.Source = _listScrollViewer;
binding.Path = new PropertyPath("VerticalOffset");
binding.Mode = BindingMode.OneWay;
this.SetBinding(ListVerticalOffsetProperty, binding); <---//(this)
}
Where this (shown above) referring the parent class which contains _listScrollViewer control, but I want to write AvailableItemListScrollViewer_Loaded method for all the pages of my app, so I am putting it into a separate class, but here this listener only receiving that ui element reference, so how can i get the parent class ? So I can write the last line of code above something like this--->
(class reference).SetBinding(ListVerticalOffsetProperty, binding)
Why not to use XAML binding here ? Smth like
<Page ListVerticalOffset="{Binding Path="VerticalOffset"
ElementName="ScrollViewerName"}/>
Simply copy this binding to every page you need with according ElementName
I've discovered what the problem was, It is nothing to do with the C# code itself, But it's in the XAML instead, The issue was the default colors that I've set in the XAML were overriding my style's colors.
So in conclusion, when you are setting any property by XAML it always overrides later styles set by C# code at runtime, this seems strange to me but at least that is how it worked for me.
The default Background colors in the XAML code avoided the C#'s style to apply on the panels (At-least avoided the new Background to be applied over the default ones).
I used your code and modified little bit for verification. Seems to be working fine. Have a look:
Style Style_Panel = new Style(typeof(Panel));
public void Init_Style()
{
// Create Styles :
#region "Create Styles"
Style_Panel.Setters.Add(new Setter()
{
Property = Panel.BackgroundProperty,
Value = new SolidColorBrush(Colors.Red)
});
Resources.Add(Style_Panel.TargetType, Style_Panel);
#endregion
// Apply Styles :
#region "Apply Styles"
List<Visual> List_Visual = new List<Visual>();
List_Visual.Add(new StackPanel() { Name = "btn" });
//Enum_Visual(Panel_Main, List_Visual);
foreach (Visual visual in List_Visual)
{
if (visual is Panel)
{
Panel panel = visual as Panel;
//if (Tagged(panel, "titlebar"))
//{
//}
//else if (Tagged(panel) == false)
{
// panel.Background = new SolidColorBrush( Colors.Red ); // <- WORKS .
panel.Style = Style_Panel; // <- DOES NOT WORKS !
}
}
}
#endregion
}
You haven't posted the creation of your style, maybe something is missing there?
There is another similar answer on StackOverflow which is a very good and short example of creating and setting a style in code:
Q: Does anyone know how to create a wpf Style in code behind, I can't find anything on the web or MSDN docs. I have tried this but it is not working:
A: You need to add setters to the style rather than using RegisterName. The following code, in the Window_Loaded event, will create a new TextBlock style which will become the default for all instances of a TextBlock within the Window. If you'd rather set it explicitly on one particular TextBlock, you can set the Style property of that control rather than adding the style to the Resources dictionary.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Style style = new Style(typeof (TextBlock));
style.Setters.Add(new Setter(TextBlock.ForegroundProperty, Brushes.Green));
style.Setters.Add(new Setter(TextBlock.TextProperty, "Green"));
Resources.Add(typeof (TextBlock), style);
}
I'm slowly learning WPF using this article and other resources.
I am focusing on the application logic - defining the model + viewModel, and creating commands that operate on these. I have not yet looked at the view and the .xaml format.
While I am working on the logic, I want to have a view that can render any viewModel I bind to it. The view should
Render any public string properties as text boxes, and bind the text box to the property
Render the name of the property as a label.
Render any public 'Command' property as a button, and bind the button to the command (perhaps only if the command takes no arguments?)
Is something like this possible while maintaing the MVVM design pattern? If so, how would I achieve it? Also, the article suggests to avoid using .xaml codebehind - can this view be implemented in pure xaml?
I don't think it is possible in XAML only. If you want to generate your views in runtime then you have to just use reflection over your ViewModels and generate controls accordingly. If you want to generate views at compile time then you can generate xaml files from your ViewModels at build time with some template engine (like T4 or string template) or CodeDom. Or you can go further and have some metadata format (or even DSL) from which you will generate both models and views and so on. It is up to your app needs.
And also in MVVM code-behind is Ok for visual logic and binding to model/viewmodel that can't be done in XAML only.
I'm not sure this is an appropriate use for a "pure MVVM" approach, certainly not everything is going to be achieved simply by binding. And I'd just throw away the idea of avoiding using code-behind for your "view" here, this is an inherently programmatic task. The one thing you should stick to is giving the ViewModel no knowledge of the view, so that when you replace it with the "real thing" there is no work to do.
But certainly seems reasonable thing to do; it almost sounds more like a debugging visualiser - you may be able to leverage an existing tool for this.
(If you did want to do this in mostly XAML with standard ItemsControls and templates you might write a converter to expose properties of your ViewModel by reflection in some form that you can bind to, a collection of wrapper objects with exposed metadata, but I think ensuring that the properties exposed are properly bindable would be more work than it's worth)
I'm halfway through implementing this now, I hope the following code will help anyone else trying to do this. It might be fun to turn into a more robust library.
AbstractView.xaml:
<UserControl x:Class="MyApplication.View.AbstractView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel Name="container">
</StackPanel>
</UserControl>
AbstractView.xaml.cs:
public partial class AbstractView : UserControl
{
public AbstractView()
{
InitializeComponent();
DataContextChanged += Changed;
}
void Changed(object sender, DependencyPropertyChangedEventArgs e)
{
object ob = e.NewValue;
var props = ob.GetType().GetProperties();
List<UIElement> uies = new List<UIElement>();
foreach (var prop in props)
{
if (prop.PropertyType == typeof(String))
uies.Add(makeStringProperty(prop));
else if (prop.PropertyType == typeof(int))
uies.Add(makeIntProperty(prop));
else if (prop.PropertyType == typeof(bool))
uies.Add(makeBoolProperty(prop));
else if (prop.PropertyType == typeof(ICommand))
uies.Add(makeCommandProperty(prop));
else
{
}
}
StackPanel st = new StackPanel();
st.Orientation = Orientation.Horizontal;
st.HorizontalAlignment = HorizontalAlignment.Center;
st.Margin = new Thickness(0, 20, 0, 0);
foreach (var uie in uies) {
if (uie is Button)
st.Children.Add(uie);
else
container.Children.Add(uie);
}
if (st.Children.Count > 0)
container.Children.Add(st);
}
UIElement makeCommandProperty(PropertyInfo prop)
{
var btn = new Button();
btn.Content = prop.Name;
var bn = new Binding(prop.Name);
btn.SetBinding(Button.CommandProperty, bn);
return btn;
}
UIElement makeBoolProperty(PropertyInfo prop)
{
CheckBox bx = new CheckBox();
bx.SetBinding(CheckBox.IsCheckedProperty, getBinding(prop));
if (!prop.CanWrite)
bx.IsEnabled = false;
return makeUniformGrid(bx, prop);
}
UIElement makeStringProperty(PropertyInfo prop)
{
TextBox bx = new TextBox();
bx.SetBinding(TextBox.TextProperty, getBinding(prop));
if (!prop.CanWrite)
bx.IsEnabled = false;
return makeUniformGrid(bx, prop);
}
UIElement makeIntProperty(PropertyInfo prop)
{
TextBlock bl = new TextBlock();
bl.SetBinding(TextBlock.TextProperty, getBinding(prop));
return makeUniformGrid(bl, prop);
}
UIElement makeUniformGrid(UIElement ctrl, PropertyInfo prop)
{
Label lb = new Label();
lb.Content = prop.Name;
UniformGrid u = new UniformGrid();
u.Rows = 1;
u.Columns = 2;
u.Children.Add(lb);
u.Children.Add(ctrl);
return u;
}
Binding getBinding(PropertyInfo prop)
{
var bn = new Binding(prop.Name);
if (prop.CanRead && prop.CanWrite)
bn.Mode = BindingMode.TwoWay;
else if (prop.CanRead)
bn.Mode = BindingMode.OneWay;
else if (prop.CanWrite)
bn.Mode = BindingMode.OneWayToSource;
return bn;
}
}
Pointer: Generate a dynamic DataTemplate as a string tied to the specific VM (Target). Parse it via XamlReader. Plonk it into your app resources in code.
Just an idea.. run with it.. Should be done by some type other than the View or the ViewModel.