Lexa

WPF, C#, Objective C and a little Math

Generate localisation .rexs files from excel file with VBA macros

I localized my WPF application using xaml markup extension (something like this). For each language one resx file is required. This was difficult to support many resx files syncronised, so I made a little VBA script for an excel file containing all translations. Now it is much easier to maintain strings – just modify excel file and save – resx files will be generated when file is saved.
Excel
First column contains ids of text strings, other columns contains translations. Here is this useful macros: Strings


WPF Binding double to TextBox Text Property

Binding to double property of some object with UpdateSourceTrigger set to PropertyChanged allows dynamically update ViewModel while user enters the number. Application become more responsive and user friendly.

Unfortuntely simple binding has frustrating editing behaviors – portion of the text are automatically erased in some cases (usually decimal separator and leading or trailing zeroes) – “cleaning up of the input”.

As an example I made a small application, converting millimeters to inches. This is how the problem looks like (please note I have German keyboard layout, decimal separator is comma):

input_problems

The reason of this is quite simple as soon as string is converted into a double value and it is assigned to ViewModel dependency property, ViewModel notifies performed update, and double value transformed back to string without “unneded” separators and zeroes. This is very annoying while entering text, so decided to fix it.

The solution is quite simple – own converter which saves user input string and returns it instead of rebuilding it every time. Something like this:

class ProxyConverterTest : IValueConverter
{
    private string user_string = null;

    public object Convert(object value, Type targetType, 
        object parameter, System.Globalization.CultureInfo culture)
    {
        if (user_string != null)
        {
            return user_string;
        }
            
        double number = (double)value;
        return string.Format(System.Globalization.CultureInfo.CurrentCulture, "{0}", number);
    }

    public object ConvertBack(object value, Type targetType, 
        object parameter, System.Globalization.CultureInfo culture)
    {
        string s_value = value.ToString();
        double result = 0;

        if (!double.TryParse(s_value, System.Globalization.NumberStyles.Number, 
            System.Globalization.CultureInfo.CurrentCulture, out result))
            return null;

        user_string = s_value;

        return result;
    }        
}

Unfortunately this will not work in all cases – in example, if bound value of ViewModel will be changed from some other place of the application (in my example – changed from the other TextBox), converter will continue delivering old string value. So when TextBox looses its focus, it makes sense to set user_string to null. Also on lost focus we can format output string – clean it up (actually the same behavior which we had at the beginning, but only on Lost Focus). To do so, an attached property is introduced:

class ProxyConverterTest : IValueConverter
{
    private string user_string = null;

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (user_string != null)
        {
            return user_string;
        }
            
        double number = (double)value;
        return string.Format(System.Globalization.CultureInfo.CurrentCulture, "{0}", number);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string s_value = value.ToString();
        double result = 0;

        if (!double.TryParse(s_value, System.Globalization.NumberStyles.Number, 
            System.Globalization.CultureInfo.CurrentCulture, out result))
            return null;

        user_string = s_value;

        return result;
    }
        
    public static DependencyProperty GetUpdateOnLostFocus(DependencyObject obj)
    {
        return (DependencyProperty)obj.GetValue(UpdateOnLostFocusProperty);
    }

    public static void SetUpdateOnLostFocus(DependencyObject obj, DependencyProperty value)
    {
        obj.SetValue(UpdateOnLostFocusProperty, value);
    }

    public static readonly DependencyProperty UpdateOnLostFocusProperty =
        DependencyProperty.RegisterAttached("UpdateOnLostFocus", typeof(DependencyProperty), 
        typeof(ProxyConverterTest), new UIPropertyMetadata(null, UpdateOnLostFocusChanged));

    private static void UpdateOnLostFocusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var element = (FrameworkElement)d;

        if (e.NewValue != null)
        {
            element.LostFocus += new RoutedEventHandler(element_LostFocus);
        }

        if (e.OldValue != null)
        {
            element.LostFocus -= new RoutedEventHandler(element_LostFocus);
        }
    }

    static void element_LostFocus(object sender, RoutedEventArgs e)
    {
        var element = (FrameworkElement)sender;
        BindingExpression bindingExpression = element.GetBindingExpression(GetUpdateOnLostFocus(element));
        Binding parentBinding = bindingExpression.ParentBinding;
        var converter = parentBinding.Converter as ViewModel.ProxyConverterTest;
        if (converter != null)
        {
            converter.user_string = null;
            bindingExpression.UpdateTarget();
        }
    }
}

Xaml code:

<TextBox view_model:ProxyConverterTest.UpdateOnLostFocus="TextBox.Text">
    <TextBox.Text>
        <Binding Path="Bar" Source="{StaticResource Foo}" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
            <Binding.Converter>
                <view_model:ProxyConverterTest/>
            </Binding.Converter>
        </Binding>                                 
    </TextBox.Text>
</TextBox>

Input looks much better now:
input_fixed

Here is the complete source code of the project: StringConversionTest. Note that ProxyConverter is extended with Scale Dependency property to implement conversion of input value from inches to millimeters.


AM: Waiting for Review

Well, meanwhile Arcade Monsterball was uploaded and is waiting for review. That was quite a hard task to complete, but it finally over.

3717847-00087

Here is my completed TODO list (what a nightmare to complete):

+ Make screenshots
+ (iOS) check for leaks
+ (iOS) iPhoneLayout
+ background of menus: flying shadows of spaceships
+ sync player as well in network game (when sending hit information)
+ (iOS) handle pause events in iOS
+ Points shall be assigned even if lost
+ (iOS) enlarge touch area to whole screen
+ (iOS) invitation of a player
+ (iOS) achivements for keeping locked players
+ bugfix – achievment and point number at the same time
+ achievements icons
+ (iOS) leaderbords: points overall and points in the single match
+ (iOS) shift upper UI elements if iAd is dsplayed
+ points of network player shall come from network player (?)
+ use bitmap fonts instead of TTF
+ help screen content
+ game win menu: add collected points submenu + unlocked players menu.
+ save selected player and position into the file
+ when creating stars: with very small probability a planet should be created (probably rotating)
+ move shadow to separate class
+ play network with AI
+ help screen:
+ should show 2-3 screens with instructions (made from single texture)
+ cleanup texture cache
+ all balls shall use the same texture
+ load different balls each serve
+ skin ball
+ different ball types
+ game data management (available players, points, etc.)
+ multiply achivement of the game on number of hits
+ skin menus in GameLayer
+ fire on enemy select screen
+ indcator for ball hits in upper region
+ player type shall be sent for network game
+ (iOS) iad advertisement
+ two fires (jump and normal) for firing monsters
+ add music to runtime
+ select enemy screen
+ different player models
+ icon
+ MainMenu UI for iPhone shall be ~20% larger than usuall scale (0.3125 -> 0.4, 0.2 for non-HD version)
+ atmosphere for the moon
+ fix problem with “Return from the othr side”
+ sky texture shall have 100 px width
+ particle systems: dust and fire
+ animated background – asteroids, space ships, asteroids collisions, Earth, etc.
+ remove bouncing around a point (see TODO in PositionPlayerControl)
+ skin for iphone/ipad “control pad”
+ finish touch player control
+ render shadows using physix (renderer) body information
+ shake ground when ball hits it
+ (iOS) vertical alignment on iPhone and iPod
+ particles
+ stars and rockets on the background
+ stars moving altogether
+ fix problems with resolution
+ Shadow of the players and the ball
+ change controls (use mouse joint)
+ use client – server architecture (including test environement)
+ Load level from json
+ Load players and a ball from json
+ introduce game state
+ the ball shall be alive a little after the score
+ the ball shall stay in air until the first touch
+ keyboard for windows
+ named walls
+ ai
++ basic ai
++ advanced ai
++ best position calculation is done not at once, but certain amount of tests each update.
++ parametrized ai
++ ai shall get tired -> more stupid after with time
+ sound
+ game menu
+ animated players (move_forward, move_backward, jump)
+ match
+ ESCAPE button for windows version
+ network game for win version
+ keyborad fix: only process if current window is active
+ pause screen during network game
+ network game timeout (no messages during N ticks) – “Player disconnected”
+ network game/single game – show current player
+ game ui: pause shall be a 2 buttons names “pause” – left and right to score
+ game ui: icons in the corner: type of the player (local, remote, AI)
+ game ui: ball picture for serving
+ game ui: hit count (for tests)
+ game ui: points
+ points for: game win (x rest hit counts), single touch (x2, x3, etc.), back wall hit (x2, x3, etc.), 2 games in a row (x3, x4, etc.)
+ menu structure design
+ points for “flawless victory”

During the project I learned quite a lot about cocos2d, box2d, R.U.B.E. and inkscape. The other fancy tool I discovered is an online bitmap font generator: http://kvazars.com/littera/. Brilliant thing.


cocos2dx addSpriteFramesWithFile problem – garbage on the screen

I noticed that in some cases after addSpriteFramesWithFile function is called, the loaded frames are displayed for 1 frame. This was is quite disturbing and exists both in Windows implemenation and on iOS devices.

I do not really know what is the reason for this (might be details of addSpriteFramesWithFile implementation), but the solution was simple: after addSpriteFramesWithFile is done, I call visit() of the current scene, and nothing unusual appears on the screen.


cocos2dx – ccDrawCircle, solid, filled?

I tried to draw a solid cicle in cocos2dx. It was quite terrible to modify ccDrawCircle for this. My app crashed with openGL errors which I was not able to handle. Few days later (after I left this idea and used spites instead) I realized that I should use ccDrawSolidPoly. What a shame :)

Here is the code anyway:

void ccDrawSolidCircle(const CCPoint& center, float radius, float angle, unsigned int segments, float scaleX, float scaleY, ccColor4F color) 
{
	// Copypaste from ccDrawCircle to build poli buffer.	
    const float coef = 2.0f * (float)M_PI/segments;

    CCPoint *poli = (CCPoint*)calloc(sizeof(CCPoint)*(segments+1), 1);
    if (!poli)
        return;

    for(unsigned int i = 0;i <= segments; i++) {
        float rads = i*coef;
	poli[i].x = radius * cosf(rads + angle) * scaleX + center.x;
        poli[i].y = radius * sinf(rads + angle) * scaleY + center.y;
    }
    ccDrawSolidPoly(poli, segments + 1, color);
}

Meanwhile, Arcade Monsterball grew up to quite colorful game. Still a lot TODOs, but major things are done:


Arcade Monsterball

Manwhile I am making a small game – a remake of the famous Arcade Volleyball.

It is portable  – I use cocos2d-x, so iOS and at least Windows versions will be available. Here is Windows demo. Network gaming is supported as well ;)

What is alse used:

  • Box2D
  • ENet for networking in Windows
  • R.U.B.E.
  • TexturePacker

iOS 7

Recently I installed a new version of the iOS. First impression could be probably described as “what the hell is this”. But quite soon I get used to it. It is definitely a step forward, it is modern, it has good new ideas. UI is also a bit buggy, but this is totally OK for a beta release. Nasty thing is that a man has to do quite some job to make an app (especially an app which uses custom stuff like icons, buttons or gradients) look OK for the iOS 7.


Box2D DebugDraw (b2Draw implementation) for WINAPI, WINGDI

Quite often I create simple or test apps using native Win32 project (WINAPI). This time I wanted an output of Box2D world. No ready-to-copypaste implementation was found in the Net, so I had to make my own, and now I can share it :)

And here is the source code:

Header file

// DebugDrawGDI.h
// 
// 
// Created by PavlAl on 06.05.2013
// Copyright (c) 2013 tatalata, http://talatala.com
// 

#include <Box2D/Box2D.h>

#pragma once

class DebugDrawGDI : public b2Draw
{
public:
	DebugDrawGDI();
	~DebugDrawGDI();

	/// Initialize drawing of a whole world.
	void Begin(RECT & winRect, HDC hdc, b2World *world);

	/// Initialize drwaing in a certain AABB.
	void Begin(RECT & winRect, HDC hdc, b2World *world, const b2AABB & aabb);

	/// Unintialize drawing.
	void End();

	/// Draw a closed polygon provided in CCW order.
	virtual void DrawPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color);

	/// Draw a solid closed polygon provided in CCW order.
	virtual void DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color);

	/// Draw a circle.
	virtual void DrawCircle(const b2Vec2& center, float32 radius, const b2Color& color);

	/// Draw a solid circle.
	virtual void DrawSolidCircle(const b2Vec2& center, float32 radius, const b2Vec2& axis, const b2Color& color);

	/// Draw a line segment.
	virtual void DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color);

	/// Draw a transform. Choose your own length scale.
	/// @param xf a transform.
	virtual void DrawTransform(const b2Transform& xf);

private:
	/// Setup current brush and pen.
	void SetColor(const b2Color& color);
	/// Delete current brush and pen.
	void CleanupGDIObjects();

private:
	/// Reference to a HDC set in Begin method. 
	HDC m_hdc;
	/// Last used brush, owned by DebugDrawGDI.
	HBRUSH m_brush;
	/// Last used pen, DebugDrawGDI.
	HPEN m_pen;
	/// Last used color.
	DWORD m_brushColor;
};

Implementation file

// DebugDrawGDI.cpp
// 
// 
// Created by PavlAl on 06.05.2013
// Copyright (c) 2013 tatalata, http://talatala.com
// 
#include "stdafx.h"
#include "DebugDrawGDI.h"
#include <vector>

#define DEBUG_DRAW_MAX_WORLD_SIZE 1000
#define SCALE 1000.f
#define UP(v) ((v) * SCALE)
#define UPi(v) ((int)UP(v))

class AABBDetector : public b2QueryCallback
{
public:
	b2AABB aabb;

	AABBDetector()
	{
		aabb.lowerBound = b2Vec2(DEBUG_DRAW_MAX_WORLD_SIZE, DEBUG_DRAW_MAX_WORLD_SIZE);
		aabb.upperBound = b2Vec2(-DEBUG_DRAW_MAX_WORLD_SIZE, -DEBUG_DRAW_MAX_WORLD_SIZE);
	}

	bool ReportFixture(b2Fixture* fixture)
	{
		b2Vec2 pos = fixture->GetBody()->GetPosition();

		if (aabb.lowerBound.x > pos.x) aabb.lowerBound.x = pos.x;
		if (aabb.upperBound.x < pos.x) aabb.upperBound.x = pos.x;
		if (aabb.lowerBound.y > pos.y) aabb.lowerBound.y = pos.y;
		if (aabb.upperBound.y < pos.y) aabb.upperBound.y = pos.y;

		return true;
	}
};

namespace
{
	b2AABB CreateAABB(float xMin, float yMin, float xMax, float yMax)
	{
		b2AABB aabb;
		aabb.lowerBound = b2Vec2(xMin, yMin);
		aabb.upperBound = b2Vec2(xMax, yMax);
		return aabb;
	}
}

DebugDrawGDI::DebugDrawGDI()
: m_hdc(NULL)
, m_brush(NULL)
, m_pen(NULL)
, m_brushColor(0)
{
}

DebugDrawGDI::~DebugDrawGDI()
{
	CleanupGDIObjects();
}

void DebugDrawGDI::Begin(RECT & winRect, HDC hdc, b2World *world, const b2AABB & aabb)
{
	m_hdc = hdc;
	world->SetDebugDraw(this);

	BOOL bOK = SetGraphicsMode(hdc, GM_ADVANCED);
	ModifyWorldTransform(hdc, NULL, MWT_IDENTITY);

	// Cleanup area.
	FillRect(hdc, &winRect, (HBRUSH)GetStockObject(WHITE_BRUSH));

	// Change world transform.
	int outputWidth = winRect.right - winRect.left;
	int outputHeight = winRect.bottom - winRect.top;
	float boundsWidth = UP(aabb.upperBound.x - aabb.lowerBound.x);
	float boundsHeight = UP(aabb.upperBound.y - aabb.lowerBound.y);
	float cx = outputWidth / boundsWidth;
	float cy = outputHeight / boundsHeight;
	float c = cx > cy ? cy : cx;

	XFORM x;
	x.eM11 = c;
	x.eM21 = 0;
	x.eDx = winRect.left + (outputWidth - boundsWidth * c) / 2 - UP(aabb.lowerBound.x) * c;
	x.eM12 = 0;
	x.eM22 = -c;
	x.eDy = winRect.top + (outputHeight - boundsHeight * c) / 2 + UP(aabb.upperBound.y) * c;

	SetWorldTransform(hdc, &x);
}

void DebugDrawGDI::Begin(RECT & winRect, HDC hdc, b2World *world)
{
	// Get world AABB.
	AABBDetector d;
	world->QueryAABB(&d, CreateAABB(-DEBUG_DRAW_MAX_WORLD_SIZE, -DEBUG_DRAW_MAX_WORLD_SIZE, DEBUG_DRAW_MAX_WORLD_SIZE, DEBUG_DRAW_MAX_WORLD_SIZE));

	d.aabb.lowerBound -= b2Vec2(1, 1);
	d.aabb.upperBound += b2Vec2(1, 1);

	Begin(winRect, hdc, world, d.aabb);
}

void DebugDrawGDI::End()
{
	ModifyWorldTransform(m_hdc, NULL, MWT_IDENTITY);
	m_hdc = NULL;
}

void DebugDrawGDI::CleanupGDIObjects()
{
	if (m_brush) ::DeleteObject(m_brush);
	if (m_pen) ::DeleteObject(m_pen);

	m_brush = NULL;
	m_pen = NULL;
}

void DebugDrawGDI::SetColor(const b2Color& color)
{
	DWORD rgb = RGB(color.r * 255, color.g*255, color.b*255);
	if (!m_brush || !m_pen || m_brushColor != rgb)
	{
		CleanupGDIObjects();

		m_brush = ::CreateSolidBrush(rgb);
		m_pen = ::CreatePen(PS_SOLID, 1, RGB(color.r * 155, color.g*155, color.b*155));
		m_brushColor = rgb;
	}

	::SelectObject(m_hdc, m_brush);
	::SelectObject(m_hdc, m_pen);
}

/// Draw a closed polygon provided in CCW order.
void DebugDrawGDI::DrawPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color)
{
    static std::vector<POINT> pts;
    pts.resize(vertexCount + 1);
    for (int i = 0; i < vertexCount; ++i, ++vertices)
    {
        POINT p;
        p.x = UPi(vertices->x);
        p.y = UPi(vertices->y);
        pts[i] = p;
    }
    pts[vertexCount] = pts.front();
    
    SetColor(color);
    ::Polyline(m_hdc, &pts[0], vertexCount + 1);
}

/// Draw a solid closed polygon provided in CCW order.
void DebugDrawGDI::DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color)
{
    static std::vector<POINT> pts;
    pts.resize(vertexCount + 1);
    for (int i = 0; i < vertexCount; ++i, ++vertices)
    {
        POINT p;
        p.x = UPi(vertices->x);
        p.y = UPi(vertices->y);
        pts[i] = p;
    }
    pts[vertexCount] = pts.front();
    
    SetColor(color);
    ::Polygon(m_hdc, &pts[0], vertexCount + 1);
}

/// Draw a circle.
void DebugDrawGDI::DrawCircle(const b2Vec2& center, float32 radius, const b2Color& color)
{
	SetColor(color);

	// Cleanup fill brush.
	::SelectObject(m_hdc, GetStockObject(NULL_BRUSH));

	::Ellipse(
		m_hdc, 
		UPi(center.x - radius),
		UPi(center.y - radius),
		UPi(center.x + radius),
		UPi(center.y + radius));

	SetColor(color);
}

/// Draw a solid circle.
void DebugDrawGDI::DrawSolidCircle(const b2Vec2& center, float32 radius, const b2Vec2& axis, const b2Color& color)
{
	SetColor(color);

	::Ellipse(
		m_hdc, 
		UPi(center.x - radius),
		UPi(center.y - radius),
		UPi(center.x + radius),
		UPi(center.y + radius));

	DrawSegment(center, center + b2Vec2(axis.x * radius, axis.y *radius), color);
}

/// Draw a line segment.
void DebugDrawGDI::DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color)
{
	SetColor(color);

	::MoveToEx(m_hdc, UPi(p1.x), UPi(p1.y), NULL);
	::LineTo(m_hdc, UPi(p2.x), UPi(p2.y));
}

/// Draw a transform. Choose your own length scale.
/// @param xf a transform.
void DebugDrawGDI::DrawTransform(const b2Transform& xf)
{
	const float length = 1;
	b2Vec2 start = xf.p;
	b2Vec2 axis = xf.q.GetXAxis();
	b2Vec2 end = start + b2Vec2(axis.x * length, axis.y * length);
	::MoveToEx(m_hdc, UPi(start.x), UPi(start.y), NULL);
	::LineTo(m_hdc, UPi(end.x), UPi(end.y));
}